commit 7465b598a80fe63955e56a3b09d0afcfdb7e3b5b
parent 43121e80950668f9f942dd90483e749a2838053b
Author: triesap <tyson@radroots.org>
Date: Fri, 2 Oct 2026 14:26:35 +0000
diagnostics: bound and sanitize support evidence
- Apply a closed diagnostic schema before every native sink
- Sanitize metadata and preserve bounded explicit support exports
- Share a private formatter across Rust file and stdout logs
- Qualify actual sink and export canaries at exact native inputs
Diffstat:
17 files changed, 516 insertions(+), 97 deletions(-)
diff --git a/Package.swift b/Package.swift
@@ -100,6 +100,7 @@ let package = Package(
"Runtime/TeraMediaProcessUse.swift",
"Runtime/TeraRemoteQualificationMediaPicker.swift",
"Runtime/TeraDiagnosticsStore.swift",
+ "Runtime/TeraDiagnosticPolicy.swift",
"Runtime/TeraGeneratedRuntimeStartup.swift",
"Runtime/TeraBackupRuntimeStartup.swift",
"Runtime/TeraBackupFiles.swift",
diff --git a/Tera.xcodeproj/project.pbxproj b/Tera.xcodeproj/project.pbxproj
@@ -118,6 +118,7 @@
C0C7D6F0CBCE262C348D3C3E /* TeraTodayReconciliationFFITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F65B01F6F5F7E125148EC4FD /* TeraTodayReconciliationFFITests.swift */; };
C20BDCF02F461EE53B14B53E /* TeraPermissionRecoveryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BF69ADD9540F6800FD64B0 /* TeraPermissionRecoveryTests.swift */; };
C219B5F4E24188E1116BF45B /* TeraRuntimeClientFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88ED360C84532DA7088BFC6E /* TeraRuntimeClientFixtures.swift */; };
+ C2CEC638DDABB9D01DD93A72 /* TeraDiagnosticPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE158F5F1E490847A5196EB /* TeraDiagnosticPolicyTests.swift */; };
C3177E827FE62639579C3EF5 /* TeraRemoteQualificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CBDAE974FB8D7408108E69 /* TeraRemoteQualificationUITests.swift */; };
C344F7761A4AC79AB74705C9 /* TeraTodayCalendarScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2BE93A0818202625CAF1F5 /* TeraTodayCalendarScopeTests.swift */; };
C46F8EB5B196FFDD37E0279A /* TeraProductStartupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF60EF9DCFDC4ECE77AED67 /* TeraProductStartupTests.swift */; };
@@ -300,6 +301,7 @@
E62DD95FB04526E6E69AB3D8 /* TeraSubmissionFormTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraSubmissionFormTests.swift; sourceTree = "<group>"; };
E75B60DBDD8C581D76E6F957 /* TeraScopedObservationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraScopedObservationTests.swift; sourceTree = "<group>"; };
ECE8A3704AB9582A5E461195 /* TeraDraftRecoveryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraDraftRecoveryTests.swift; sourceTree = "<group>"; };
+ EDE158F5F1E490847A5196EB /* TeraDiagnosticPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraDiagnosticPolicyTests.swift; sourceTree = "<group>"; };
EE1A300DCBB15E1911EA7D6C /* TeraCoordinateAdmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraCoordinateAdmissionTests.swift; sourceTree = "<group>"; };
EFBAA57ABEAA4B84C73EE8E8 /* TeraScopedMediaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraScopedMediaTests.swift; sourceTree = "<group>"; };
EFECCBF9A84D1D65544C6094 /* TeraTodayContextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeraTodayContextTests.swift; sourceTree = "<group>"; };
@@ -406,6 +408,7 @@
DE21EEF3B1A13F1B5A10A3BF /* TeraComposerStartupTests.swift */,
56A65AF934F7D75487094C79 /* TeraComposerSubmissionCaptureTests.swift */,
EE1A300DCBB15E1911EA7D6C /* TeraCoordinateAdmissionTests.swift */,
+ EDE158F5F1E490847A5196EB /* TeraDiagnosticPolicyTests.swift */,
ECE8A3704AB9582A5E461195 /* TeraDraftRecoveryTests.swift */,
22BEF874B8D521920E1D16B7 /* TeraDurableMediaRootsTests.swift */,
AC9B19425D3374B899485C17 /* TeraEditingOperationProtectionTests.swift */,
@@ -739,6 +742,7 @@
3410EC660FF36CC77F5F50DD /* TeraComposerStartupTests.swift in Sources */,
C73B145B6858220FFFA587B2 /* TeraComposerSubmissionCaptureTests.swift in Sources */,
207C19433F0386DD7D43A42D /* TeraCoordinateAdmissionTests.swift in Sources */,
+ C2CEC638DDABB9D01DD93A72 /* TeraDiagnosticPolicyTests.swift in Sources */,
64F5AD3749E090316C7D015E /* TeraDraftRecoveryTests.swift in Sources */,
80F3B531DAA8C2F6CE17D357 /* TeraDurableMediaRootsTests.swift in Sources */,
F14217EA66C2A9397E36E3FA /* TeraEditingOperationProtectionTests.swift in Sources */,
diff --git a/Tera/Runtime/TeraDiagnosticPolicy.swift b/Tera/Runtime/TeraDiagnosticPolicy.swift
@@ -0,0 +1,75 @@
+import Foundation
+import RadrootsKit
+
+/// An app-owned closed schema, applied before all sinks and again at the
+/// standalone buffer boundary. Pattern redaction alone cannot admit content.
+enum TeraDiagnosticPolicy {
+ static let eventNames: Set<String> = [
+ "ios.lifecycle.start_requested", "ios.lifecycle.resume_coalesced",
+ "ios.lifecycle.active", "ios.lifecycle.background",
+ "ios.lifecycle.protected_data_available", "ios.lifecycle.protected_data_unavailable",
+ "ios.lifecycle.operation_failed", "ios.lifecycle.operation_completed",
+ "ios.lifecycle.shutdown_requested", "ios.diagnostics.redacted",
+ ]
+ static let phases: Set<String> = [
+ "starting", "identity_required", "identity_locked", "protected_data_unavailable",
+ "recovery_required", "corrupt_identity", "configuration_reconfiguration_required",
+ "running", "stopped", "failed",
+ ]
+ private static let operations: Set<String> = [
+ "start", "stop", "identity_create", "identity_import", "identity_lock", "identity_unlock",
+ "identity_recover", "configuration_reconfigure", "settings_reconfigure", "identity_key_removal",
+ ]
+
+ static func sanitized(_ event: RadrootsTelemetryEvent) -> RadrootsTelemetryEvent? {
+ let known = eventNames.contains(event.name)
+ let name = known ? event.name : "ios.diagnostics.redacted"
+ let fields = known ? event.fields.prefix(8).compactMap(sanitizedField) : []
+ return try? RadrootsTelemetryEvent(
+ name: name, category: "ios_lifecycle", level: event.level,
+ fields: fields, occurredAt: event.occurredAt
+ )
+ }
+
+ private static func sanitizedField(_ field: RadrootsTelemetryField) -> RadrootsTelemetryField? {
+ if case let .string(value) = field.value {
+ switch field.key {
+ case "operation" where operations.contains(value): return field
+ case "phase" where phases.contains(value): return field
+ case "code" where value == "bootstrap_failed": return field
+ default: return nil
+ }
+ }
+ if case let .integer(value) = field.value,
+ ["count", "attempts", "schema", "latency_ms"].contains(field.key),
+ (0 ... 1_000_000).contains(value)
+ {
+ return field
+ }
+ return nil
+ }
+
+ static func code(_ value: String, allowed: Set<String>) -> String {
+ allowed.contains(value) ? value : "unavailable"
+ }
+
+ static func version(_ value: String) -> String {
+ code(value, allowed: ["0.1.0-alpha"])
+ }
+
+ static func build(_ value: String) -> String {
+ guard !value.isEmpty, value.utf8.count <= 10,
+ value.utf8.allSatisfy({ (48 ... 57).contains($0) })
+ else { return "unavailable" }
+ return value
+ }
+}
+
+struct TeraSanitizedTelemetry: RadrootsTelemetry {
+ let sink: any RadrootsTelemetry
+
+ func record(_ event: RadrootsTelemetryEvent) async {
+ guard let event = TeraDiagnosticPolicy.sanitized(event) else { return }
+ await sink.record(event)
+ }
+}
diff --git a/Tera/Runtime/TeraLifecycleCoordinator.swift b/Tera/Runtime/TeraLifecycleCoordinator.swift
@@ -111,7 +111,6 @@ actor TeraDiagnosticsBuffer: RadrootsTelemetry {
}
private let capacity: Int
- private let policy = RadrootsTelemetryRedactionPolicy.default
private var events: [Entry] = []
init(capacity: Int = 128) {
@@ -119,6 +118,7 @@ actor TeraDiagnosticsBuffer: RadrootsTelemetry {
}
func record(_ event: RadrootsTelemetryEvent) {
+ guard let event = TeraDiagnosticPolicy.sanitized(event) else { return }
guard let occurredAtUnixMilliseconds = try? TeraClock.signedUnixMilliseconds(
from: event.occurredAt
) else {
@@ -126,7 +126,7 @@ actor TeraDiagnosticsBuffer: RadrootsTelemetry {
}
events.append(
Entry(
- event: policy.redacted(event),
+ event: event,
occurredAtUnixMilliseconds: occurredAtUnixMilliseconds
)
)
@@ -142,11 +142,11 @@ actor TeraDiagnosticsBuffer: RadrootsTelemetry {
name: event.name,
category: event.category,
level: event.level.rawValue,
- fields: Dictionary(
- uniqueKeysWithValues: event.fields.map { field in
- (field.key, field.value.renderedValue)
+ fields: event.fields.reduce(into: [String: String]()) { values, field in
+ if values[field.key] == nil {
+ values[field.key] = field.value.renderedValue
}
- ),
+ },
occurredAtUnixMilliseconds: entry.occurredAtUnixMilliseconds
)
}
@@ -189,7 +189,7 @@ actor TeraLifecycleCoordinator {
transfer: (any RadrootsBackgroundTransfer)?,
transferIdentifier: String?
) {
- self.telemetry = telemetry
+ self.telemetry = TeraSanitizedTelemetry(sink: telemetry)
self.buffer = buffer
self.fileAccess = fileAccess
self.transfer = transfer
@@ -211,7 +211,7 @@ actor TeraLifecycleCoordinator {
let logger = RadrootsAppleLoggerTelemetry(subsystem: bundleIdentifier)
let telemetry = RadrootsMultiplexTelemetry([
logger,
- RadrootsRedactingTelemetry(sink: buffer),
+ buffer,
])
let identifier = try RadrootsBackgroundTransferValidation.normalizedIdentifier(
TeraRemoteQualificationEnvironment.backgroundTransferIdentifier(
@@ -238,7 +238,7 @@ actor TeraLifecycleCoordinator {
static func disabled() -> TeraLifecycleCoordinator {
let buffer = TeraDiagnosticsBuffer()
return TeraLifecycleCoordinator(
- telemetry: RadrootsRedactingTelemetry(sink: buffer),
+ telemetry: buffer,
buffer: buffer,
fileAccess: nil,
transfer: nil,
@@ -251,7 +251,7 @@ actor TeraLifecycleCoordinator {
{
let buffer = TeraDiagnosticsBuffer(capacity: capacity)
return TeraLifecycleCoordinator(
- telemetry: RadrootsRedactingTelemetry(sink: buffer),
+ telemetry: buffer,
buffer: buffer,
fileAccess: RadrootsAppleFileAccess(roots: roots),
transfer: nil,
@@ -296,7 +296,7 @@ actor TeraLifecycleCoordinator {
level: RadrootsTelemetryLevel = .info,
fields: [String: String] = [:]
) async {
- let values = fields.sorted(by: { $0.key < $1.key }).compactMap { key, value in
+ let values = fields.prefix(8).sorted(by: { $0.key < $1.key }).compactMap { key, value in
try? RadrootsTelemetryField.string(key, value)
}
guard
@@ -330,13 +330,13 @@ actor TeraLifecycleCoordinator {
let records = await buffer.records()
let document = TeraDiagnosticsDocument(
schema: "radroots.ios.diagnostics.v1",
- appVersion: appVersion,
- appBuild: appBuild,
- runtimeCrate: snapshot.crateName,
- runtimeVersion: snapshot.crateVersion,
- runtimePhase: phase,
- relayProfile: snapshot.relay?.profile,
- relayState: snapshot.relay?.state,
+ appVersion: TeraDiagnosticPolicy.version(appVersion),
+ appBuild: TeraDiagnosticPolicy.build(appBuild),
+ runtimeCrate: TeraDiagnosticPolicy.code(snapshot.crateName, allowed: ["tera_ffi"]),
+ runtimeVersion: TeraDiagnosticPolicy.version(snapshot.crateVersion),
+ runtimePhase: TeraDiagnosticPolicy.code(phase, allowed: TeraDiagnosticPolicy.phases),
+ relayProfile: snapshot.relay.map { TeraDiagnosticPolicy.code($0.profile, allowed: ["public", "simulator_local", "simulator", "device_development", "unknown"]) },
+ relayState: snapshot.relay.map { TeraDiagnosticPolicy.code($0.state, allowed: ["configured", "connecting", "read_only", "writable", "degraded", "offline", "failed", "closed", "unknown"]) },
relayCount: snapshot.relay?.relays.count ?? 0,
records: records
)
diff --git a/TeraFFI/provenance.json b/TeraFFI/provenance.json
@@ -22,9 +22,9 @@
"sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c"
},
{
- "bytes": 79562256,
+ "bytes": 79610952,
"path": "TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a",
- "sha256": "5c1a6aa24e17b0a663a4cf18cdac0fff5c8579dc9dc8218625f2e65088b6db15"
+ "sha256": "648555098c7aa327a3f3e2e113a5868aef6d438c985a1f2c73e7f015349944a7"
},
{
"bytes": 105627,
@@ -37,9 +37,9 @@
"sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c"
},
{
- "bytes": 79590016,
+ "bytes": 79646800,
"path": "TeraFFI.xcframework/ios-arm64/libtera_ffi.a",
- "sha256": "ccbdc32bd64fd669f874c87b51b8faea76cd5a8fca7a981eabdc63bad0de3536"
+ "sha256": "d7b5bce06f98b202c676e2022377d006459be8572aa1bccf4d902e5247b69238"
},
{
"bytes": 64990,
@@ -77,34 +77,34 @@
"sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c"
},
{
- "bytes": 23629376,
+ "bytes": 23644912,
"path": "native/aarch64-apple-darwin/libtera_ffi.dylib",
- "sha256": "91fa528d7e5cf05594ab07971135c416390e3db4060f3eba42571b01976c09ee"
+ "sha256": "30d66d614f0aadfd6ab870f4281f2986a2dfd93f4046bb6a12257d83153c7a9c"
},
{
- "bytes": 79562256,
+ "bytes": 79610952,
"path": "native/aarch64-apple-ios-sim/libtera_ffi.a",
- "sha256": "5c1a6aa24e17b0a663a4cf18cdac0fff5c8579dc9dc8218625f2e65088b6db15"
+ "sha256": "648555098c7aa327a3f3e2e113a5868aef6d438c985a1f2c73e7f015349944a7"
},
{
- "bytes": 79590016,
+ "bytes": 79646800,
"path": "native/aarch64-apple-ios/libtera_ffi.a",
- "sha256": "ccbdc32bd64fd669f874c87b51b8faea76cd5a8fca7a981eabdc63bad0de3536"
+ "sha256": "d7b5bce06f98b202c676e2022377d006459be8572aa1bccf4d902e5247b69238"
},
{
- "bytes": 137498,
+ "bytes": 138031,
"path": "source/aarch64-apple-darwin.json",
- "sha256": "a8c261d06aa74c41acdbbc9fc04ece8b82d2eb1a913c8a0d660be90dcc4a1b59"
+ "sha256": "c227dc0072bf5f333f0da087fd72906cb657d8b3be4442c032292b6ebc625321"
},
{
- "bytes": 137342,
+ "bytes": 137875,
"path": "source/aarch64-apple-ios-sim.json",
- "sha256": "a50bfff47625d38b61181db481bd1162e985f1a0506a1f04c5a8e4be9fe99d63"
+ "sha256": "267f13ec71cfccda3d2d9bc6600fab446fde9fda4b7e0afd31011aeabe96785e"
},
{
- "bytes": 137338,
+ "bytes": 137871,
"path": "source/aarch64-apple-ios.json",
- "sha256": "2c620e09300f6b23da57978a04d153f18dc7b4f101081810b2eff467db636392"
+ "sha256": "c1dc9e9a167368169dfd74cef909497c204a41c7da76d00a12f5ba4fbbcf7c85"
}
],
"language": "swift",
@@ -112,7 +112,7 @@
"schema": "radroots.artifact-manifest.v2",
"source": {
"repository": "https://github.com/radrootslabs/tera",
- "tree": "82b8d14996d9b088101ca9f21c82c1200d737d96"
+ "tree": "8f9abc83606e9996524f519b8bfb18dc3348590a"
},
"source_records": {
"aarch64-apple-darwin": "source/aarch64-apple-darwin.json",
@@ -139,9 +139,9 @@
"sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c"
},
{
- "bytes": 79562256,
+ "bytes": 79610952,
"path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a",
- "sha256": "5c1a6aa24e17b0a663a4cf18cdac0fff5c8579dc9dc8218625f2e65088b6db15"
+ "sha256": "648555098c7aa327a3f3e2e113a5868aef6d438c985a1f2c73e7f015349944a7"
},
{
"bytes": 105627,
@@ -154,9 +154,9 @@
"sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c"
},
{
- "bytes": 79590016,
+ "bytes": 79646800,
"path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64/libtera_ffi.a",
- "sha256": "ccbdc32bd64fd669f874c87b51b8faea76cd5a8fca7a981eabdc63bad0de3536"
+ "sha256": "d7b5bce06f98b202c676e2022377d006459be8572aa1bccf4d902e5247b69238"
},
{
"bytes": 807948,
@@ -174,19 +174,19 @@
"sha256": "5d74060f97e52dad69ab7567c16d6b6b34acfab846f468450d89e2394a8a69d5"
},
{
- "bytes": 137498,
+ "bytes": 138031,
"path": "TeraFFI/source/aarch64-apple-darwin.json",
- "sha256": "a8c261d06aa74c41acdbbc9fc04ece8b82d2eb1a913c8a0d660be90dcc4a1b59"
+ "sha256": "c227dc0072bf5f333f0da087fd72906cb657d8b3be4442c032292b6ebc625321"
},
{
- "bytes": 137342,
+ "bytes": 137875,
"path": "TeraFFI/source/aarch64-apple-ios-sim.json",
- "sha256": "a50bfff47625d38b61181db481bd1162e985f1a0506a1f04c5a8e4be9fe99d63"
+ "sha256": "267f13ec71cfccda3d2d9bc6600fab446fde9fda4b7e0afd31011aeabe96785e"
},
{
- "bytes": 137338,
+ "bytes": 137871,
"path": "TeraFFI/source/aarch64-apple-ios.json",
- "sha256": "2c620e09300f6b23da57978a04d153f18dc7b4f101081810b2eff467db636392"
+ "sha256": "c1dc9e9a167368169dfd74cef909497c204a41c7da76d00a12f5ba4fbbcf7c85"
}
],
"schema": "tera.installed-native-artifacts.v1"
diff --git a/TeraFFI/source.lock b/TeraFFI/source.lock
@@ -1,7 +1,7 @@
schema = "tera.installed-source.v1"
repository = "https://github.com/radrootslabs/tera"
-source_tree = "82b8d14996d9b088101ca9f21c82c1200d737d96"
-manifest_sha256 = "f85af700b201d5fbe036a489bb42e8ab88c63b8efb4f6c78e279131e1190b27d"
+source_tree = "8f9abc83606e9996524f519b8bfb18dc3348590a"
+manifest_sha256 = "feaf4681644f041ce4bf841cf1e3c29b03bba15c9de9858f1c0a1ccf63419993"
source_date_epoch = 1787871027
[foundation]
diff --git a/TeraFFI/source/aarch64-apple-darwin.json b/TeraFFI/source/aarch64-apple-darwin.json
@@ -2092,10 +2092,16 @@
"sha256": "d9eccaf768ee3907498b399c317ad5a2e26d73e345cf55a952eabe4f5c508775"
},
"core/crates/tera_ffi/src/logging.rs": {
- "bytes": 9313,
- "git_blob": "aaed371f0f074bfa2d2ccb6d5e958c75d066fff6",
+ "bytes": 9488,
+ "git_blob": "c54092b280d199f3d8178d9d986569eb88a7d107",
"mode": "100644",
- "sha256": "59911489707924bb42302894800a77551f9038b56f783e4c634bd66d800f92a2"
+ "sha256": "39a0bef89336f974062bacba8140db2dd1c14c3353a3ae256caddeb293523c85"
+ },
+ "core/crates/tera_ffi/src/logging/privacy_format.rs": {
+ "bytes": 2342,
+ "git_blob": "b6e2c643a541c42de081a35e71f3772c8699b543",
+ "mode": "100644",
+ "sha256": "d699ff796db7e0c49f736a0a24915702d25725c9f19f4551b6771f6de8f8cced"
},
"core/crates/tera_ffi/src/logging/writer.rs": {
"bytes": 15077,
@@ -2337,6 +2343,12 @@
"mode": "100644",
"sha256": "ec87c2cc7f396ef583d3a37186f40932ac4eb42c0e80973a5c25312099f2670a"
},
+ "core/crates/tera_ffi/tests/logging_privacy.rs": {
+ "bytes": 3917,
+ "git_blob": "540fb276cddf7d2760c1ef6b7f2d6c332fd8968c",
+ "mode": "100644",
+ "sha256": "0a8ef753b0ebec0be7bdff235b2c0b3da3461a1b1240ca3765d6da5c8597fe27"
+ },
"core/crates/tera_ffi/tests/media_ownership.rs": {
"bytes": 6567,
"git_blob": "57a09e25c1db661ecacb26afe95491f462cb52f8",
@@ -2920,13 +2932,13 @@
"sha256": "4e174e14512c1b12994dea63f58bf39d234d96c3bf245eb5d7eaa0fcf2b4fd24"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 186460,
- "git_blob": "ca81e3e1c1628650dceb01ebd2d3e6046c7a9b00",
+ "bytes": 186929,
+ "git_blob": "39860837f76433ef305fde969d9162fefa98e579",
"mode": "100644",
- "sha256": "7f5053f449d49ff9220e2ba57db5749c813dfe40f04965b0107ce7e49a15966c"
+ "sha256": "0e7b8f85e58a1b34c27ebbaa03b21f30e54efd9f4998462dda8b2bba93cb54df"
}
},
"policy": "staged_inputs",
- "tree": "82b8d14996d9b088101ca9f21c82c1200d737d96"
+ "tree": "8f9abc83606e9996524f519b8bfb18dc3348590a"
}
}
diff --git a/TeraFFI/source/aarch64-apple-ios-sim.json b/TeraFFI/source/aarch64-apple-ios-sim.json
@@ -2088,10 +2088,16 @@
"sha256": "d9eccaf768ee3907498b399c317ad5a2e26d73e345cf55a952eabe4f5c508775"
},
"core/crates/tera_ffi/src/logging.rs": {
- "bytes": 9313,
- "git_blob": "aaed371f0f074bfa2d2ccb6d5e958c75d066fff6",
+ "bytes": 9488,
+ "git_blob": "c54092b280d199f3d8178d9d986569eb88a7d107",
"mode": "100644",
- "sha256": "59911489707924bb42302894800a77551f9038b56f783e4c634bd66d800f92a2"
+ "sha256": "39a0bef89336f974062bacba8140db2dd1c14c3353a3ae256caddeb293523c85"
+ },
+ "core/crates/tera_ffi/src/logging/privacy_format.rs": {
+ "bytes": 2342,
+ "git_blob": "b6e2c643a541c42de081a35e71f3772c8699b543",
+ "mode": "100644",
+ "sha256": "d699ff796db7e0c49f736a0a24915702d25725c9f19f4551b6771f6de8f8cced"
},
"core/crates/tera_ffi/src/logging/writer.rs": {
"bytes": 15077,
@@ -2333,6 +2339,12 @@
"mode": "100644",
"sha256": "ec87c2cc7f396ef583d3a37186f40932ac4eb42c0e80973a5c25312099f2670a"
},
+ "core/crates/tera_ffi/tests/logging_privacy.rs": {
+ "bytes": 3917,
+ "git_blob": "540fb276cddf7d2760c1ef6b7f2d6c332fd8968c",
+ "mode": "100644",
+ "sha256": "0a8ef753b0ebec0be7bdff235b2c0b3da3461a1b1240ca3765d6da5c8597fe27"
+ },
"core/crates/tera_ffi/tests/media_ownership.rs": {
"bytes": 6567,
"git_blob": "57a09e25c1db661ecacb26afe95491f462cb52f8",
@@ -2916,13 +2928,13 @@
"sha256": "4e174e14512c1b12994dea63f58bf39d234d96c3bf245eb5d7eaa0fcf2b4fd24"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 186460,
- "git_blob": "ca81e3e1c1628650dceb01ebd2d3e6046c7a9b00",
+ "bytes": 186929,
+ "git_blob": "39860837f76433ef305fde969d9162fefa98e579",
"mode": "100644",
- "sha256": "7f5053f449d49ff9220e2ba57db5749c813dfe40f04965b0107ce7e49a15966c"
+ "sha256": "0e7b8f85e58a1b34c27ebbaa03b21f30e54efd9f4998462dda8b2bba93cb54df"
}
},
"policy": "staged_inputs",
- "tree": "82b8d14996d9b088101ca9f21c82c1200d737d96"
+ "tree": "8f9abc83606e9996524f519b8bfb18dc3348590a"
}
}
diff --git a/TeraFFI/source/aarch64-apple-ios.json b/TeraFFI/source/aarch64-apple-ios.json
@@ -2088,10 +2088,16 @@
"sha256": "d9eccaf768ee3907498b399c317ad5a2e26d73e345cf55a952eabe4f5c508775"
},
"core/crates/tera_ffi/src/logging.rs": {
- "bytes": 9313,
- "git_blob": "aaed371f0f074bfa2d2ccb6d5e958c75d066fff6",
+ "bytes": 9488,
+ "git_blob": "c54092b280d199f3d8178d9d986569eb88a7d107",
"mode": "100644",
- "sha256": "59911489707924bb42302894800a77551f9038b56f783e4c634bd66d800f92a2"
+ "sha256": "39a0bef89336f974062bacba8140db2dd1c14c3353a3ae256caddeb293523c85"
+ },
+ "core/crates/tera_ffi/src/logging/privacy_format.rs": {
+ "bytes": 2342,
+ "git_blob": "b6e2c643a541c42de081a35e71f3772c8699b543",
+ "mode": "100644",
+ "sha256": "d699ff796db7e0c49f736a0a24915702d25725c9f19f4551b6771f6de8f8cced"
},
"core/crates/tera_ffi/src/logging/writer.rs": {
"bytes": 15077,
@@ -2333,6 +2339,12 @@
"mode": "100644",
"sha256": "ec87c2cc7f396ef583d3a37186f40932ac4eb42c0e80973a5c25312099f2670a"
},
+ "core/crates/tera_ffi/tests/logging_privacy.rs": {
+ "bytes": 3917,
+ "git_blob": "540fb276cddf7d2760c1ef6b7f2d6c332fd8968c",
+ "mode": "100644",
+ "sha256": "0a8ef753b0ebec0be7bdff235b2c0b3da3461a1b1240ca3765d6da5c8597fe27"
+ },
"core/crates/tera_ffi/tests/media_ownership.rs": {
"bytes": 6567,
"git_blob": "57a09e25c1db661ecacb26afe95491f462cb52f8",
@@ -2916,13 +2928,13 @@
"sha256": "4e174e14512c1b12994dea63f58bf39d234d96c3bf245eb5d7eaa0fcf2b4fd24"
},
"test-fixtures/legacy-identifiers.v1.json": {
- "bytes": 186460,
- "git_blob": "ca81e3e1c1628650dceb01ebd2d3e6046c7a9b00",
+ "bytes": 186929,
+ "git_blob": "39860837f76433ef305fde969d9162fefa98e579",
"mode": "100644",
- "sha256": "7f5053f449d49ff9220e2ba57db5749c813dfe40f04965b0107ce7e49a15966c"
+ "sha256": "0e7b8f85e58a1b34c27ebbaa03b21f30e54efd9f4998462dda8b2bba93cb54df"
}
},
"policy": "staged_inputs",
- "tree": "82b8d14996d9b088101ca9f21c82c1200d737d96"
+ "tree": "8f9abc83606e9996524f519b8bfb18dc3348590a"
}
}
diff --git a/TeraTests/TeraDiagnosticPolicyTests.swift b/TeraTests/TeraDiagnosticPolicyTests.swift
@@ -0,0 +1,69 @@
+import Foundation
+import RadrootsKit
+@testable import TeraApp
+import XCTest
+
+final class TeraDiagnosticPolicyTests: XCTestCase {
+ func testAllSinksReceiveOnlyClosedFieldsAndNoRawMessageCategoryOrValues() async throws {
+ let capture = DiagnosticSink()
+ let buffer = TeraDiagnosticsBuffer()
+ let telemetry = TeraSanitizedTelemetry(sink: RadrootsMultiplexTelemetry([capture, buffer]))
+ let canary = "ordinary private text 49.123456,-123.987654 Bearer CANARY_TOKEN /Users/private/file"
+ let event = try RadrootsTelemetryEvent(
+ name: "ios.lifecycle.operation_completed", category: "private_category", message: canary,
+ fields: [
+ .string("operation", "start"), .string("phase", "running"),
+ .string("code", canary), .string("authorization", canary),
+ .string("plain", canary), .integer("count", 7), .integer("id", 123_456_789),
+ .stringList("list", [canary]), .double("latitude", 49.123456),
+ ]
+ )
+ await telemetry.record(event)
+ let observed = await capture.events
+ let safe = try XCTUnwrap(observed.first)
+ XCTAssertNil(safe.message)
+ XCTAssertEqual(safe.category, "ios_lifecycle")
+ XCTAssertEqual(safe.fields.map(\.key), ["operation", "phase", "count"])
+ XCTAssertFalse(String(describing: safe).contains(canary))
+ let records = await buffer.records()
+ XCTAssertEqual(records.first?.fields, ["operation": "start", "phase": "running", "count": "7"])
+ XCTAssertEqual(TeraDiagnosticPolicy.sanitized(safe), safe)
+ }
+
+ func testDirectBufferRejectsUnknownNamesKeysAndHighCardinalityValues() async throws {
+ let buffer = TeraDiagnosticsBuffer(capacity: 16)
+ for index in 0 ..< 40 {
+ let event = try RadrootsTelemetryEvent(
+ name: "private.post.\(index)", category: "private_location",
+ message: "Raw private content", fields: [.string("unknown", "Bearer canary")]
+ )
+ await buffer.record(event)
+ }
+ let records = await buffer.records()
+ XCTAssertEqual(records.count, 16)
+ XCTAssertTrue(records.allSatisfy { $0.name == "ios.diagnostics.redacted" && $0.category == "ios_lifecycle" && $0.fields.isEmpty })
+ let event = try RadrootsTelemetryEvent(name: "ios.lifecycle.active", fields: [
+ .integer("count", Int64.max), .integer("schema", -1), .string("phase", "secret"),
+ ])
+ await buffer.record(event)
+ let final = await buffer.records()
+ XCTAssertEqual(final.last?.fields, [:])
+ }
+
+ func testMetadataUsesFiniteCodesAndBoundedNumericBuilds() {
+ let canary = "public key or private content /Users/person/file"
+ XCTAssertEqual(TeraDiagnosticPolicy.version(canary), "unavailable")
+ XCTAssertEqual(TeraDiagnosticPolicy.version("0.1.0-alpha"), "0.1.0-alpha")
+ XCTAssertEqual(TeraDiagnosticPolicy.build(canary), "unavailable")
+ XCTAssertEqual(TeraDiagnosticPolicy.build(String(repeating: "1", count: 11)), "unavailable")
+ XCTAssertEqual(TeraDiagnosticPolicy.build("1"), "1")
+ XCTAssertEqual(TeraDiagnosticPolicy.code(canary, allowed: TeraDiagnosticPolicy.phases), "unavailable")
+ }
+}
+
+private actor DiagnosticSink: RadrootsTelemetry {
+ var events: [RadrootsTelemetryEvent] = []
+ func record(_ event: RadrootsTelemetryEvent) {
+ events.append(event)
+ }
+}
diff --git a/TeraTests/TeraLifecycleTests.swift b/TeraTests/TeraLifecycleTests.swift
@@ -115,7 +115,8 @@ final class TeraLifecycleTests: XCTestCase {
XCTAssertEqual(object["schema"] as? String, "radroots.ios.diagnostics.v1")
XCTAssertEqual(records.count, 16)
XCTAssertLessThanOrEqual(data.count, 256 * 1024)
- XCTAssertTrue(text.contains("[redacted]"))
+ XCTAssertTrue(text.contains("ios.diagnostics.redacted"))
+ XCTAssertTrue(records.allSatisfy { ($0["fields"] as? [String: String])?.isEmpty == true })
XCTAssertFalse(text.contains(secret))
XCTAssertFalse(text.contains(privateKey))
XCTAssertFalse(text.contains(privatePath))
@@ -125,6 +126,28 @@ final class TeraLifecycleTests: XCTestCase {
XCTAssertFalse(FileManager.default.fileExists(atPath: export.fileURL.path))
}
+ func testActualExportExcludesUntrustedMetadataAndOrdinaryPrivateContent() async throws {
+ let roots = try makeRoots()
+ defer { try? FileManager.default.removeItem(at: roots.dataRoot.deletingLastPathComponent()) }
+ let coordinator = TeraLifecycleCoordinator.testing(roots: roots)
+ let canary = "plain post at 49.123456,-123.987654 Bearer CANARY /Users/private/file"
+ await coordinator.record("private_event", fields: ["value": canary, "phase": canary])
+ let export = try await coordinator.prepareDiagnostics(
+ snapshot: makeSnapshot(privateKey: String(repeating: "ab", count: 32), metadata: canary),
+ appVersion: canary, appBuild: canary, phase: canary
+ )
+ let data = try Data(contentsOf: export.fileURL)
+ let text = try XCTUnwrap(String(data: data, encoding: .utf8))
+ XCTAssertFalse(text.contains(canary))
+ let document = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any])
+ for key in ["appVersion", "appBuild", "runtimeCrate", "runtimeVersion", "runtimePhase", "relayProfile", "relayState"] {
+ XCTAssertEqual(document[key] as? String, "unavailable", key)
+ }
+ XCTAssertLessThanOrEqual(data.count, 256 * 1024)
+ await coordinator.releaseDiagnostics(export)
+ XCTAssertFalse(FileManager.default.fileExists(atPath: export.fileURL.path))
+ }
+
private func makeRoots() throws -> RadrootsAppleFileRoots {
let base = FileManager.default.temporaryDirectory
.appendingPathComponent(
@@ -138,15 +161,15 @@ final class TeraLifecycleTests: XCTestCase {
)
}
- private func makeSnapshot(privateKey: String) -> TeraRuntimeSnapshot {
+ private func makeSnapshot(privateKey: String, metadata: String? = nil) -> TeraRuntimeSnapshot {
TeraRuntimeSnapshot(
identity: TeraRuntimeIdentity(
publicKeyHex: privateKey,
hostSignerConfigured: true
),
relay: TeraRelayStatus(
- profile: "simulator",
- state: "configured",
+ profile: metadata ?? "simulator",
+ state: metadata ?? "configured",
readAvailability: "available",
writeAvailability: "available",
relays: [
@@ -164,8 +187,8 @@ final class TeraLifecycleTests: XCTestCase {
),
blossomConfiguration: nil,
blossomEvidence: nil,
- crateName: "tera_ffi",
- crateVersion: "0.1.0-alpha",
+ crateName: metadata ?? "tera_ffi",
+ crateVersion: metadata ?? "0.1.0-alpha",
isClosed: false
)
}
diff --git a/core/crates/tera_ffi/src/logging.rs b/core/crates/tera_ffi/src/logging.rs
@@ -1,3 +1,4 @@
+mod privacy_format;
mod writer;
use std::fs;
@@ -76,12 +77,12 @@ fn initialize(options: LoggingOptions) -> Result<(), String> {
tracing_subscriber::fmt::layer()
.with_writer(writer.clone())
.with_ansi(false)
- .with_target(false)
+ .event_format(privacy_format::DiagnosticFormat)
});
let stdout_layer = options.stdout.then(|| {
tracing_subscriber::fmt::layer()
.with_writer(std::io::stdout)
- .with_target(false)
+ .event_format(privacy_format::DiagnosticFormat)
});
tracing_subscriber::registry()
.with(file_layer)
@@ -148,19 +149,22 @@ pub fn init_logging_stdout() -> Result<(), crate::TeraAppError> {
#[cfg_attr(not(coverage_nightly), uniffi::export)]
pub fn log_info(msg: String) -> Result<(), crate::TeraAppError> {
- tracing::info!("{msg}");
+ drop(msg);
+ tracing::info!(code = "message_redacted");
Ok(())
}
#[cfg_attr(not(coverage_nightly), uniffi::export)]
pub fn log_error(msg: String) -> Result<(), crate::TeraAppError> {
- tracing::error!("{msg}");
+ drop(msg);
+ tracing::error!(code = "message_redacted");
Ok(())
}
#[cfg_attr(not(coverage_nightly), uniffi::export)]
pub fn log_debug(msg: String) -> Result<(), crate::TeraAppError> {
- tracing::debug!("{msg}");
+ drop(msg);
+ tracing::debug!(code = "message_redacted");
Ok(())
}
diff --git a/core/crates/tera_ffi/src/logging/privacy_format.rs b/core/crates/tera_ffi/src/logging/privacy_format.rs
@@ -0,0 +1,81 @@
+//! Closed diagnostics at both native logging sinks. Raw event messages,
+//! span fields, targets, identifiers and paths are never formatted.
+
+use std::fmt;
+use tracing::{
+ Event, Subscriber,
+ field::{Field, Visit},
+};
+use tracing_subscriber::{
+ fmt::{FmtContext, FormatEvent, FormatFields, format::Writer},
+ registry::LookupSpan,
+};
+
+pub(super) struct DiagnosticFormat;
+
+#[derive(Default)]
+struct DiagnosticFields {
+ code: Option<&'static str>,
+ counts: Vec<(&'static str, u64)>,
+}
+
+impl Visit for DiagnosticFields {
+ fn record_debug(&mut self, _field: &Field, _value: &dyn fmt::Debug) {}
+
+ fn record_str(&mut self, field: &Field, value: &str) {
+ if field.name() == "code" {
+ self.code = match value {
+ "message_redacted" => Some("message_redacted"),
+ "runtime_started" => Some("runtime_started"),
+ "runtime_stopped" => Some("runtime_stopped"),
+ "operation_failed" => Some("operation_failed"),
+ _ => None,
+ };
+ }
+ }
+
+ fn record_u64(&mut self, field: &Field, value: u64) {
+ let key = match field.name() {
+ "count" => "count",
+ "attempts" => "attempts",
+ "schema" => "schema",
+ "latency_ms" => "latency_ms",
+ _ => return,
+ };
+ if value <= 1_000_000 && self.counts.len() < 4 {
+ self.counts.push((key, value));
+ }
+ }
+
+ fn record_i64(&mut self, field: &Field, value: i64) {
+ if let Ok(value) = u64::try_from(value) {
+ self.record_u64(field, value);
+ }
+ }
+}
+
+impl<S, N> FormatEvent<S, N> for DiagnosticFormat
+where
+ S: Subscriber + for<'lookup> LookupSpan<'lookup>,
+ N: for<'writer> FormatFields<'writer> + 'static,
+{
+ fn format_event(
+ &self,
+ _context: &FmtContext<'_, S, N>,
+ mut writer: Writer<'_>,
+ event: &Event<'_>,
+ ) -> fmt::Result {
+ let mut fields = DiagnosticFields::default();
+ event.record(&mut fields);
+ write!(
+ writer,
+ "level={} code={}",
+ event.metadata().level(),
+ fields.code.unwrap_or("message_redacted")
+ )?;
+ for (key, value) in fields.counts {
+ write!(writer, " {key}={value}")?;
+ }
+ writeln!(writer)
+ }
+}
diff --git a/core/crates/tera_ffi/tests/logging_privacy.rs b/core/crates/tera_ffi/tests/logging_privacy.rs
@@ -0,0 +1,107 @@
+use std::process::{Command, Stdio};
+use std::time::{Duration, Instant};
+
+const CHILD_ROOT: &str = "TERA_LOG_PRIVACY_CHILD_ROOT";
+const TEST_NAME: &str = "actual_file_and_stdout_sinks_exclude_private_material";
+const CANARIES: [&str; 7] = [
+ "plain private post text without a redaction keyword",
+ "Bearer CANARY_AUTHORIZATION_VALUE",
+ "49.123456,-123.987654 precise location",
+ "/Users/private/canary.json",
+ "nsec1CANARY_SECRET",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "PRIVATE_TARGET_CANARY",
+];
+
+#[test]
+fn actual_file_and_stdout_sinks_exclude_private_material() {
+ if let Some(root) = std::env::var_os(CHILD_ROOT) {
+ emit_and_drain(std::path::Path::new(&root));
+ return;
+ }
+ let root = tempfile::tempdir().unwrap();
+ let mut child = Command::new(std::env::current_exe().unwrap())
+ .args(["--exact", TEST_NAME, "--nocapture"])
+ .env(CHILD_ROOT, root.path())
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ .unwrap();
+ let deadline = Instant::now() + Duration::from_secs(10);
+ loop {
+ if child.try_wait().unwrap().is_some() {
+ break;
+ }
+ if Instant::now() >= deadline {
+ child.kill().unwrap();
+ child.wait().unwrap();
+ panic!("bounded diagnostics sink process did not finish");
+ }
+ std::thread::sleep(Duration::from_millis(10));
+ }
+ let output = child.wait_with_output().unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ let stderr = String::from_utf8(output.stderr).unwrap();
+ let file = std::fs::read_to_string(root.path().join("privacy.log")).unwrap();
+ assert_safe(&stdout);
+ assert_safe(&file);
+ for canary in CANARIES {
+ assert!(!stderr.contains(canary));
+ }
+ let lines = stdout
+ .lines()
+ .filter(|line| line.starts_with("level="))
+ .collect::<Vec<_>>();
+ assert_eq!(lines.len(), 6);
+ assert_eq!(lines, file.lines().collect::<Vec<_>>());
+}
+
+fn emit_and_drain(root: &std::path::Path) {
+ tera_ffi::logging::init_logging(
+ Some(root.to_str().unwrap().to_owned()),
+ Some("privacy.log".to_owned()),
+ Some(true),
+ )
+ .unwrap();
+ tera_ffi::logging::log_info(CANARIES[0].to_owned()).unwrap();
+ tera_ffi::logging::log_error(CANARIES[1].to_owned()).unwrap();
+ tera_ffi::logging::log_debug(CANARIES[2].to_owned()).unwrap();
+ let span = tracing::info_span!("private_span", path = CANARIES[3], secret = CANARIES[4]);
+ let _entered = span.enter();
+ tracing::error!(target: "PRIVATE_TARGET_CANARY", authorization=CANARIES[1], content=CANARIES[0], location=CANARIES[2], id=CANARIES[5], message=CANARIES[3]);
+ tracing::info!(
+ code = "runtime_started",
+ count = 7_u64,
+ attempts = 2_i64,
+ schema = 1_u64,
+ latency_ms = 9_u64
+ );
+ tracing::warn!(code = CANARIES[0], count = u64::MAX, unknown = CANARIES[4]);
+ let path = root.join("privacy.log");
+ let deadline = Instant::now() + Duration::from_secs(5);
+ loop {
+ let text = std::fs::read_to_string(&path).unwrap_or_default();
+ if text.lines().count() >= 6 {
+ assert_safe(&text);
+ return;
+ }
+ assert!(
+ Instant::now() < deadline,
+ "bounded owned writer did not drain"
+ );
+ std::thread::sleep(Duration::from_millis(10));
+ }
+}
+
+fn assert_safe(text: &str) {
+ for canary in CANARIES {
+ assert!(!text.contains(canary));
+ }
+ assert!(!text.contains("private_span"));
+ assert!(!text.contains(&u64::MAX.to_string()));
+ assert!(text.contains("code=runtime_started count=7 attempts=2 schema=1 latency_ms=9"));
+ assert!(text.contains("level=ERROR code=message_redacted"));
+ assert!(text.len() < 4096);
+}
diff --git a/project.yml b/project.yml
@@ -163,6 +163,7 @@ targets:
- path: TeraTests/TeraCapacityRecoveryTests.swift
- path: TeraTests/TeraCapacitySettingsTests.swift
- path: TeraTests/TeraLifecycleTests.swift
+ - path: TeraTests/TeraDiagnosticPolicyTests.swift
- path: TeraTests/TeraClockTests.swift
- path: TeraTests/TeraCalendarFFICompatibilityTests.swift
- path: TeraTests/TeraCalendarTimingTests.swift
diff --git a/release/provenance.json b/release/provenance.json
@@ -2,11 +2,11 @@
"artifacts": {
"app_api_sha256": "020924097c0d7efc33128cb8fd3d3b2026d95f57c44da71880e585aff80f070b",
"ffi_api_sha256": "5d74060f97e52dad69ab7567c16d6b6b34acfab846f468450d89e2394a8a69d5",
- "ffi_provenance_sha256": "f85af700b201d5fbe036a489bb42e8ab88c63b8efb4f6c78e279131e1190b27d",
+ "ffi_provenance_sha256": "feaf4681644f041ce4bf841cf1e3c29b03bba15c9de9858f1c0a1ccf63419993",
"info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef",
"privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12",
"sbom_sha256": "9e243176e3eaec256f404207d284314e8c96d3c6861077acb8e760640047ee2f",
- "xcode_project_sha256": "e5346577077bbb48a52caf97c7088d2cbebddca5a76b70855afd60c7aa99cc68"
+ "xcode_project_sha256": "6ad5a40e50a198c65afa5e5f78d229c951b618d8ee98e56e49aa5ee7596ff763"
},
"disposition": "unsigned",
"platforms": [
@@ -22,7 +22,7 @@
"lib_revision": "189c49b74b4bafc142b00b76b296477931139e72",
"source_date_epoch": 1787871027,
"swift_package_lock_sha256": "322eaec80d4b85ef9eb20da95ff95c4d26d8555ca358ba0c8eb890adf0b6671e",
- "tera_ffi_source_tree": "82b8d14996d9b088101ca9f21c82c1200d737d96",
+ "tera_ffi_source_tree": "8f9abc83606e9996524f519b8bfb18dc3348590a",
"xcode_package_lock_sha256": "4e29fed46339d5e382fa78ec85b61be4296455ece4ddc40602d793c049e673b3"
},
"version": "0.1.0-alpha"
diff --git a/test-fixtures/legacy-identifiers.v1.json b/test-fixtures/legacy-identifiers.v1.json
@@ -1765,6 +1765,10 @@
"count": 1
},
{
+ "path": "Tera/Runtime/TeraDiagnosticPolicy.swift",
+ "count": 1
+ },
+ {
"path": "Tera/Runtime/TeraDiagnosticsStore.swift",
"count": 1
},
@@ -1881,6 +1885,10 @@
"count": 1
},
{
+ "path": "TeraTests/TeraDiagnosticPolicyTests.swift",
+ "count": 1
+ },
+ {
"path": "TeraTests/TeraDurableMediaRootsTests.swift",
"count": 1
},
@@ -2195,6 +2203,10 @@
{
"path": "Tera/Runtime/TeraLifecycleCoordinator.swift",
"count": 1
+ },
+ {
+ "path": "TeraTests/TeraDiagnosticPolicyTests.swift",
+ "count": 1
}
]
},
@@ -2257,16 +2269,6 @@
]
},
{
- "identifier": "RadrootsRedactingTelemetry",
- "category": "shared_apple_api",
- "occurrences": [
- {
- "path": "Tera/Runtime/TeraLifecycleCoordinator.swift",
- "count": 3
- }
- ]
- },
- {
"identifier": "RadrootsRhiEvidenceAttestationOutcomeV1",
"category": "shared_foundation_api",
"occurrences": [
@@ -2425,8 +2427,16 @@
"category": "shared_apple_api",
"occurrences": [
{
+ "path": "Tera/Runtime/TeraDiagnosticPolicy.swift",
+ "count": 2
+ },
+ {
"path": "Tera/Runtime/TeraLifecycleCoordinator.swift",
"count": 3
+ },
+ {
+ "path": "TeraTests/TeraDiagnosticPolicyTests.swift",
+ "count": 1
}
]
},
@@ -2455,27 +2465,35 @@
"category": "shared_apple_api",
"occurrences": [
{
+ "path": "Tera/Runtime/TeraDiagnosticPolicy.swift",
+ "count": 4
+ },
+ {
"path": "Tera/Runtime/TeraLifecycleCoordinator.swift",
"count": 3
},
{
+ "path": "TeraTests/TeraDiagnosticPolicyTests.swift",
+ "count": 5
+ },
+ {
"path": "TeraTests/TeraLifecycleTests.swift",
"count": 1
}
]
},
{
- "identifier": "RadrootsTelemetryField.string",
+ "identifier": "RadrootsTelemetryField",
"category": "shared_apple_api",
"occurrences": [
{
- "path": "Tera/Runtime/TeraLifecycleCoordinator.swift",
- "count": 1
+ "path": "Tera/Runtime/TeraDiagnosticPolicy.swift",
+ "count": 2
}
]
},
{
- "identifier": "RadrootsTelemetryLevel",
+ "identifier": "RadrootsTelemetryField.string",
"category": "shared_apple_api",
"occurrences": [
{
@@ -2485,7 +2503,7 @@
]
},
{
- "identifier": "RadrootsTelemetryRedactionPolicy.default",
+ "identifier": "RadrootsTelemetryLevel",
"category": "shared_apple_api",
"occurrences": [
{