Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7add260
feat(swift-sdk): add core wallet balance diagnostics
llbartekll Sep 1, 2026
2ed8c1d
fix(swift-sdk): keep deep core diagnostics off startup
llbartekll Sep 4, 2026
345134c
fix(swift-sdk): address diagnostics review
llbartekll Sep 4, 2026
33a7f2b
fix(swift-sdk): address core wallet diagnostics review
llbartekll Sep 7, 2026
e27c024
fix(swift-sdk): scope the migration fallback to the store's version, …
llbartekll Sep 7, 2026
9d75b93
fix(swift-sdk): lower the exact-audit transaction ceiling to 10k
llbartekll Sep 7, 2026
8fc2003
fix(swift-sdk): refuse the migration fallback for newer stores, and r…
llbartekll Sep 7, 2026
81fc3bf
fix(swift-sdk): bound the migration fallback to the entities known to…
llbartekll Sep 7, 2026
fa4b6a7
fix(swift-sdk): pin the drifted entities by hash, not by name
llbartekll Sep 7, 2026
7fd84c0
feat(swift-sdk): surface a refused newer-build store as a typed error
llbartekll Sep 7, 2026
668a707
fix(swift-sdk): drain both halves of the export, snapshot committed s…
llbartekll Sep 7, 2026
d86a763
fix(swift-sdk): never claim drift from a comparison that did not happen
llbartekll Sep 8, 2026
abde130
fix(swift-sdk): only claim "newer build" where the evidence has a dir…
llbartekll Sep 8, 2026
4d0533e
Merge branch 'v4.2-dev' into codex/cj-balance-diagnostics-sdk
llbartekll Sep 8, 2026
c0469ac
fix(swift-sdk): drain only what this manager can finish
llbartekll Sep 8, 2026
2eb5437
Merge remote-tracking branch 'origin/codex/cj-balance-diagnostics-sdk…
llbartekll Sep 8, 2026
d0cddbc
test(swift-sdk): stop pinning the drift reason to the whole entity list
llbartekll Sep 8, 2026
3e64599
Merge remote-tracking branch 'origin/v4.2-dev' into codex/cj-balance-…
llbartekll Sep 8, 2026
5cbb732
fix(swift-sdk): drop the "newer build" claim entirely — nothing here …
llbartekll Sep 8, 2026
c686477
fix(swift-sdk): judge each address pool only where accounts should ha…
llbartekll Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/swift-sdk/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ let package = Package(
.testTarget(
name: "SwiftDashSDKTests",
dependencies: ["SwiftDashSDK"],
path: "SwiftTests/SwiftDashSDKTests"
path: "SwiftTests/SwiftDashSDKTests",
resources: [.copy("Fixtures")]
),

// Integration tests against a local dashmate devnet.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,21 +213,55 @@ enum SDKLogFormatter {
}
}

private final class SDKLoggerState: @unchecked Sendable {
/// Internal rather than private so the pre-install buffer can be tested on a
/// fresh instance: the process-wide `SDKLogger.state` has no way back to the
/// "no sink installed" condition once any test has installed one.
final class SDKLoggerState: @unchecked Sendable {
/// How many pre-install events are retained for replay. A host that never
/// installs a sink must not accumulate lines for the life of the process,
/// so once full the buffer keeps what it has, drops the NEWEST arrivals,
/// and reports the loss. Head-not-tail on purpose: the lines this buffer
/// exists for — `core_store_open_result` from the host's `init()` — are
/// the first ones in, and a launch that overflows does so with restore
/// and changeset lines whose loss costs far less than the store open's.
static let pendingLineLimit = 256

private let lock = NSLock()
private var sink: SDKLogFileSink?
private var includeDebug = false

func installSink(at sessionDirectory: URL, includeDebug: Bool) -> Bool {
/// Events emitted before the file sink exists. `DashModelContainer.create`
/// runs in the host's `init()`, long before `LoggingPreferences.configure()`
/// installs the sink, so without this buffer the store-open result — and
/// every other launch-path event — would only ever reach the console and
/// never the exported `swift/run.log`.
private var pendingLines: [(severity: SDKLogSeverity, line: String)] = []
private var droppedPendingLineCount = 0

/// Installs the sink and replays what was emitted before it existed, in
/// emission order and under the sink's own debug filter.
func installSink(at sessionDirectory: URL, includeDebug: Bool) -> (
installed: Bool,
droppedPendingLineCount: Int
) {
do {
let newSink = try SDKLogFileSink(sessionDirectory: sessionDirectory)
lock.withLock {
// Replay under the same lock `record` takes, so a line emitted
// concurrently with the install cannot land in front of the
// backlog it actually followed.
let dropped: Int = lock.withLock {
sink = newSink
self.includeDebug = includeDebug
for entry in pendingLines where entry.severity != .debug || includeDebug {
newSink.write(entry.line)
}
let droppedCount = droppedPendingLineCount
pendingLines = []
droppedPendingLineCount = 0
return droppedCount
}
return true
return (installed: true, droppedPendingLineCount: dropped)
} catch {
return false
return (installed: false, droppedPendingLineCount: 0)
}
}

Expand All @@ -237,16 +271,40 @@ private final class SDKLoggerState: @unchecked Sendable {
}
}

func destination(for severity: SDKLogSeverity) -> SDKLogFileSink? {
lock.withLock {
/// Routes one formatted line to the sink, or buffers it for replay when no
/// sink has been installed yet.
func record(severity: SDKLogSeverity, line: String) {
Comment thread
llbartekll marked this conversation as resolved.
let destination: SDKLogFileSink? = lock.withLock {
guard let sink else {
guard pendingLines.count < Self.pendingLineLimit else {
droppedPendingLineCount += 1
return nil
}
pendingLines.append((severity: severity, line: line))
return nil
}
guard severity != .debug || includeDebug else { return nil }
return sink
}
destination?.write(line)
}

func flush() {
lock.withLock { sink }?.flush()
}

/// Test seam: drop the sink and everything buffered for it. `record`
/// buffers process-wide while no sink exists and `installSink` replays
/// the whole backlog into whichever session installs first, so a suite
/// asserting over a complete `run.log` must start from nothing.
func reset() {
lock.withLock {
sink = nil
includeDebug = false
pendingLines = []
droppedPendingLineCount = 0
}
}
}

// MARK: - Logging Preferences
Expand Down Expand Up @@ -473,7 +531,7 @@ public enum SDKLogger {
redacting: sensitiveValues
)

state.destination(for: severity)?.write(line)
state.record(severity: severity, line: line)

let shouldMirrorToConsole: Bool
switch severity {
Expand All @@ -496,13 +554,36 @@ public enum SDKLogger {
}

static func installFileSink(at sessionDirectory: URL, includeDebug: Bool) -> Bool {
state.installSink(at: sessionDirectory, includeDebug: includeDebug)
let outcome = state.installSink(
at: sessionDirectory,
includeDebug: includeDebug
)
if outcome.droppedPendingLineCount > 0 {
// Emitted after the replay so the gap is visible at the point in
// the file where the missing lines would have been.
event(
"log_pre_install_buffer_overflow",
category: .lifecycle,
severity: .warning,
fields: [
"dropped_line_count": .integer(
Int64(outcome.droppedPendingLineCount)
),
]
)
}
return outcome.installed
}

static func updateDebugSetting(_ includeDebug: Bool) {
state.updateDebugSetting(includeDebug)
}

/// Tests only. See `SDKLoggerState.reset()`.
static func resetForTesting() {
state.reset()
}

public static func log(_ message: String, minimumLevel level: LoggingPreset = .medium) {
guard LoggingPreferences.allows(level) else { return }
// Mirror to NSLog (unified logging) in addition to stdout so
Expand Down
Loading
Loading