sneakoscope 8.2.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +46 -110
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/command-manifest-lite.js +0 -2
  5. package/dist/cli/command-registry.js +0 -2
  6. package/dist/commands/codex.js +3 -3
  7. package/dist/commands/doctor.js +5 -157
  8. package/dist/config/skills-manifest.json +1 -1
  9. package/dist/core/codex/codex-cli-syntax-builder.js +0 -8
  10. package/dist/core/codex-app/codex-models-cache.js +2 -1
  11. package/dist/core/codex-app/menubar/constants.js +0 -7
  12. package/dist/core/codex-compat/codex-capability-matrix.js +7 -7
  13. package/dist/core/codex-compat/codex-compat-report.js +8 -12
  14. package/dist/core/codex-compat/codex-runtime-contract.js +62 -0
  15. package/dist/core/codex-compat/codex-version-policy.js +7 -7
  16. package/dist/core/codex-control/codex-current-app-capability.js +3 -3
  17. package/dist/core/codex-control/codex-current-capability.js +67 -94
  18. package/dist/core/codex-control/codex-current-core-capability.js +3 -3
  19. package/dist/core/codex-control/codex-current-core-probe-runner.js +4 -4
  20. package/dist/core/codex-control/codex-current-core-real-probes.js +2 -2
  21. package/dist/core/codex-control/codex-current-feature-capability.js +4 -4
  22. package/dist/core/codex-control/codex-current-image-path-real-probe.js +1 -1
  23. package/dist/core/codex-control/codex-current-web-search-probe.js +1 -1
  24. package/dist/core/codex-control/codex-sdk-capability.js +2 -2
  25. package/dist/core/commands/remote-command.js +2 -2
  26. package/dist/core/context7-client.js +5 -2
  27. package/dist/core/doctor/codex-current-core-probe-doctor.js +2 -2
  28. package/dist/core/doctor/doctor-dirty-planner.js +1 -1
  29. package/dist/core/doctor/doctor-readiness-matrix.js +0 -7
  30. package/dist/core/feature-fixtures.js +0 -4
  31. package/dist/core/feature-registry.js +0 -1
  32. package/dist/core/image-ux-review.js +3 -3
  33. package/dist/core/install/installed-package-smoke.js +2 -3
  34. package/dist/core/mad-sks/sql-plane/mcp-executor.js +4 -3
  35. package/dist/core/mcp/modern-protocol.js +36 -0
  36. package/dist/core/mcp-config/health-check.js +45 -30
  37. package/dist/core/perf/release-latency-slo.js +1 -2
  38. package/dist/core/proof/runtime-truth-matrix.js +1 -1
  39. package/dist/core/release/canonical-test-proof.js +1 -2
  40. package/dist/core/release/gate-manifest.js +5 -2
  41. package/dist/core/release/physical-release-gates.js +0 -26
  42. package/dist/core/release/release-gate-affected-selector.js +2 -1
  43. package/dist/core/release/release-gate-contract.js +3 -1
  44. package/dist/core/release/release-pack-receipt.js +0 -1
  45. package/dist/core/release/stage-publish.js +1 -1
  46. package/dist/core/version-manager.js +4 -0
  47. package/dist/core/version.js +1 -1
  48. package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -12
  49. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -6
  50. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +28 -471
  51. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +1 -43
  52. package/dist/scripts/codex-current-app-server-v2-check.js +9 -8
  53. package/dist/scripts/codex-current-binary-identity-check.js +2 -2
  54. package/dist/scripts/codex-current-capability-check.js +9 -7
  55. package/dist/scripts/codex-current-dependency-check.js +21 -0
  56. package/dist/scripts/docs-truthfulness-check.js +4 -19
  57. package/dist/scripts/fast-codex-service-tier-proof-check.js +0 -1
  58. package/dist/scripts/install-surface-ssot-check.js +2 -2
  59. package/dist/scripts/latest-version-guidance-check.js +1 -1
  60. package/dist/scripts/packlist-performance-check.js +0 -1
  61. package/dist/scripts/release-metadata-check.js +4 -1
  62. package/dist/scripts/release-version-truth-check.js +149 -0
  63. package/dist/vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json +6 -0
  64. package/dist/vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json +2 -16
  65. package/dist/vendor/openai-codex/latest/hooks/post-compact.command.input.schema.json +6 -0
  66. package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.input.schema.json +6 -0
  67. package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.output.schema.json +2 -16
  68. package/dist/vendor/openai-codex/latest/hooks/pre-compact.command.input.schema.json +6 -0
  69. package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.input.schema.json +6 -0
  70. package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.output.schema.json +2 -16
  71. package/dist/vendor/openai-codex/latest/hooks/session-start.command.output.schema.json +2 -16
  72. package/dist/vendor/openai-codex/latest/hooks/snapshot-metadata.json +7 -5
  73. package/dist/vendor/openai-codex/latest/hooks/subagent-start.command.output.schema.json +4 -18
  74. package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.input.schema.json +6 -0
  75. package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.output.schema.json +2 -16
  76. package/package.json +4 -7
  77. package/release-gates.v2.json +49 -19
  78. package/config/codex-releases/rust-v0.146.0.json +0 -45
  79. package/dist/core/agents/agent-runner-codex-exec.js +0 -177
  80. package/dist/core/codex-compat/codex-release-manifest.js +0 -72
  81. package/dist/core/commands/telegram-command.js +0 -447
  82. package/dist/core/telegram/access.js +0 -58
  83. package/dist/core/telegram/client.js +0 -157
  84. package/dist/core/telegram/confirmation.js +0 -37
  85. package/dist/core/telegram/doctor.js +0 -154
  86. package/dist/core/telegram/index.js +0 -9
  87. package/dist/core/telegram/keychain.js +0 -317
  88. package/dist/core/telegram/liveness.js +0 -124
  89. package/dist/core/telegram/poller.js +0 -90
  90. package/dist/core/telegram/redaction.js +0 -15
  91. package/dist/core/telegram/state-lock.js +0 -416
  92. package/dist/core/telegram/types.js +0 -1
  93. package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +0 -498
  94. package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +0 -236
  95. package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +0 -454
  96. package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +0 -56
  97. package/dist/native/sks-menubar/Sources/TelegramStateLock.swift +0 -448
  98. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +0 -243
  99. package/dist/native/sks-menubar/Sources/TelegramTransport.swift +0 -499
  100. package/dist/scripts/codex-current-manifest-check.js +0 -29
  101. package/schemas/codex/app-server-0.146/codex_app_server_protocol.v2.schemas.json +0 -19858
@@ -1,498 +0,0 @@
1
- import Foundation
2
-
3
- struct TelegramStoredConfirmation: Sendable, Equatable {
4
- let request: TelegramTypedCommandRequest
5
- }
6
-
7
- enum TelegramTokenSource: String, Codable, Sendable {
8
- case environment = "env"
9
- case userSecretFile = "user_secret_file"
10
- case none
11
- }
12
-
13
- struct TelegramResolvedAccessToken: Sendable, Equatable {
14
- let token: String?
15
- let source: TelegramTokenSource
16
- }
17
-
18
- struct TelegramBotBinding: Sendable, Equatable {
19
- let botID: Int64
20
- let previousBotID: Int64?
21
- let pollOffset: Int64
22
- let stateReset: Bool
23
- }
24
-
25
- enum TelegramTokenResolutionError: Error {
26
- case invalid(source: TelegramTokenSource)
27
- }
28
-
29
- protocol TelegramAccessStoring: Sendable {
30
- func resolveToken() throws -> TelegramResolvedAccessToken
31
- func bindBotIdentity(_ botID: Int64) throws -> TelegramBotBinding
32
- func persistPollOffset(_ offset: Int64, botID: Int64) throws
33
- func consumePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool
34
- func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool
35
- func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool
36
- func abandonPendingPairing(chatID: Int64, senderID: Int64) throws
37
- func isAuthorized(chatID: Int64, senderID: Int64) throws -> Bool
38
- func authorizedCount() throws -> Int
39
- func issueConfirmation(
40
- chatID: Int64,
41
- senderID: Int64,
42
- request: TelegramTypedCommandRequest,
43
- expiresAt: Date
44
- ) throws -> String
45
- func consumeConfirmation(nonce: String, chatID: Int64, senderID: Int64) throws -> TelegramStoredConfirmation?
46
- }
47
-
48
- extension TelegramAccessStoring {
49
- // Compatibility for deterministic/injected stores. The production private
50
- // store overrides these with a durable inactive-to-active transition.
51
- func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
52
- try consumePairing(code: code, chatID: chatID, senderID: senderID, chatType: chatType)
53
- }
54
- func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool { true }
55
- func abandonPendingPairing(chatID: Int64, senderID: Int64) throws { }
56
- }
57
-
58
- final class TelegramPrivateFileStore: TelegramAccessStoring, @unchecked Sendable {
59
- static let stateSchema = "sks.telegram-state.v1"
60
- static let pairingSchema = "sks.telegram-pairing.v1"
61
- static let tokenEnvironmentNames = ["TELEGRAM_BOT_TOKEN", "SKS_TELEGRAM_BOT_TOKEN"]
62
-
63
- private struct PairingState {
64
- let schema: String
65
- let code: String
66
- let expiresAt: String
67
- var used: Bool
68
- var raw: [String: Any]
69
- }
70
-
71
- private struct AuthorizedChat {
72
- let chatID: Int64
73
- let senderID: Int64
74
- let pairedAt: String
75
- var active: Bool
76
- var raw: [String: Any]
77
- }
78
-
79
- private struct ConfirmationState {
80
- let nonce: String
81
- let chatID: Int64
82
- let senderID: Int64
83
- let command: String
84
- let inputJSON: String
85
- let expiresAt: String
86
- var raw: [String: Any]
87
- }
88
-
89
- private struct State {
90
- let schema: String
91
- var botID: Int64?
92
- var pollOffset: Int64
93
- var pairing: PairingState?
94
- var chats: [AuthorizedChat]
95
- var confirmations: [ConfirmationState]
96
- var raw: [String: Any]
97
-
98
- init(
99
- schema: String = TelegramPrivateFileStore.stateSchema,
100
- botID: Int64? = nil,
101
- pollOffset: Int64 = 0,
102
- pairing: PairingState? = nil,
103
- chats: [AuthorizedChat] = [],
104
- confirmations: [ConfirmationState] = [],
105
- raw: [String: Any] = [:]
106
- ) {
107
- self.schema = schema
108
- self.botID = botID
109
- self.pollOffset = pollOffset
110
- self.pairing = pairing
111
- self.chats = chats
112
- self.confirmations = confirmations
113
- self.raw = raw
114
- }
115
- }
116
-
117
- private let environment: [String: String]
118
- private let files: TelegramPrivateFileSupport
119
- private let now: @Sendable () -> Date
120
-
121
- init(
122
- homeDirectory: URL? = nil,
123
- environment: [String: String] = ProcessInfo.processInfo.environment,
124
- now: @escaping @Sendable () -> Date = { Date() }
125
- ) {
126
- self.environment = environment
127
- self.files = TelegramPrivateFileSupport(homeDirectory: homeDirectory, environment: environment)
128
- self.now = now
129
- }
130
-
131
- static func operatorEnvironmentOverrideActive(
132
- _ environment: [String: String] = ProcessInfo.processInfo.environment
133
- ) -> Bool {
134
- tokenEnvironmentNames.contains {
135
- !(environment[$0] ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
136
- }
137
- }
138
-
139
- func resolveToken() throws -> TelegramResolvedAccessToken {
140
- for name in Self.tokenEnvironmentNames {
141
- let value = (environment[name] ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
142
- if !value.isEmpty {
143
- guard let token = try? validatedToken(value) else {
144
- throw TelegramTokenResolutionError.invalid(source: .environment)
145
- }
146
- return TelegramResolvedAccessToken(token: token, source: .environment)
147
- }
148
- }
149
- guard let data = try files.readTokenData() else {
150
- return TelegramResolvedAccessToken(token: nil, source: .none)
151
- }
152
- guard let value = String(data: data, encoding: .utf8) else {
153
- throw TelegramTokenResolutionError.invalid(source: .userSecretFile)
154
- }
155
- let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
156
- guard let token = try? validatedToken(normalized) else {
157
- throw TelegramTokenResolutionError.invalid(source: .userSecretFile)
158
- }
159
- return TelegramResolvedAccessToken(token: token, source: .userSecretFile)
160
- }
161
-
162
- func loadToken() throws -> String? {
163
- do { return try resolveToken().token }
164
- catch is TelegramTokenResolutionError { throw TelegramPrivateFileError.invalidStoredValue }
165
- }
166
-
167
- func bindBotIdentity(_ botID: Int64) throws -> TelegramBotBinding {
168
- guard botID > 0 else { throw TelegramPrivateFileError.invalidStoredValue }
169
- return try files.withStateTransaction {
170
- var state = try loadStateUnlocked()
171
- let previousBotID = state.botID
172
- let reset = previousBotID != botID || state.chats.count > 1 || state.chats.contains { !$0.active }
173
- state.botID = botID
174
- if reset {
175
- state.pollOffset = 0
176
- state.pairing = nil
177
- state.chats = []
178
- state.confirmations = []
179
- }
180
- try writeStateUnlocked(state)
181
- return TelegramBotBinding(
182
- botID: botID,
183
- previousBotID: previousBotID,
184
- pollOffset: state.pollOffset,
185
- stateReset: reset
186
- )
187
- }
188
- }
189
-
190
- func persistPollOffset(_ offset: Int64, botID: Int64) throws {
191
- guard offset >= 0, botID > 0 else { throw TelegramPrivateFileError.invalidStoredValue }
192
- try files.withStateTransaction {
193
- var state = try loadStateUnlocked()
194
- guard state.botID == botID else { throw TelegramPrivateFileError.invalidStoredValue }
195
- state.pollOffset = offset
196
- try writeStateUnlocked(state)
197
- }
198
- }
199
-
200
- func consumePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
201
- try replacePairing(
202
- code: code, chatID: chatID, senderID: senderID,
203
- chatType: chatType, active: true
204
- )
205
- }
206
-
207
- func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
208
- try replacePairing(
209
- code: code, chatID: chatID, senderID: senderID,
210
- chatType: chatType, active: false
211
- )
212
- }
213
-
214
- func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool {
215
- guard chatID > 0, senderID > 0 else { return false }
216
- return try files.withStateTransaction {
217
- var state = try loadStateUnlocked()
218
- guard state.chats.count == 1,
219
- state.chats[0].chatID == chatID,
220
- state.chats[0].senderID == senderID,
221
- !state.chats[0].active else { return false }
222
- state.chats[0].active = true
223
- try writeStateUnlocked(state)
224
- return true
225
- }
226
- }
227
-
228
- func abandonPendingPairing(chatID: Int64, senderID: Int64) throws {
229
- try files.withStateTransaction {
230
- var state = try loadStateUnlocked()
231
- let originalCount = state.chats.count
232
- state.chats.removeAll {
233
- $0.chatID == chatID && $0.senderID == senderID && !$0.active
234
- }
235
- if state.chats.count != originalCount { try writeStateUnlocked(state) }
236
- }
237
- }
238
-
239
- private func replacePairing(
240
- code: String,
241
- chatID: Int64,
242
- senderID: Int64,
243
- chatType: String,
244
- active: Bool
245
- ) throws -> Bool {
246
- guard chatType == "private", chatID > 0, senderID > 0 else { return false }
247
- return try files.withStateTransaction {
248
- var state = try loadStateUnlocked()
249
- guard var pairing = state.pairing,
250
- pairing.code == code,
251
- !pairing.used,
252
- let expiration = parseTelegramDate(pairing.expiresAt),
253
- expiration > now() else { return false }
254
- pairing.used = true
255
- state.pairing = pairing
256
- state.chats = [AuthorizedChat(
257
- chatID: chatID,
258
- senderID: senderID,
259
- pairedAt: telegramPrivateStoreDate(now()),
260
- active: active,
261
- raw: [:]
262
- )]
263
- state.confirmations = []
264
- try writeStateUnlocked(state)
265
- return true
266
- }
267
- }
268
-
269
- func isAuthorized(chatID: Int64, senderID: Int64) throws -> Bool {
270
- try files.withStateTransaction {
271
- try loadStateUnlocked().chats.contains {
272
- $0.chatID == chatID && $0.senderID == senderID && $0.active
273
- }
274
- }
275
- }
276
-
277
- func authorizedCount() throws -> Int {
278
- try files.withStateTransaction { try loadStateUnlocked().chats.filter(\.active).count }
279
- }
280
-
281
- func issueConfirmation(
282
- chatID: Int64,
283
- senderID: Int64,
284
- request: TelegramTypedCommandRequest,
285
- expiresAt: Date
286
- ) throws -> String {
287
- try files.withStateTransaction {
288
- var state = try loadStateUnlocked()
289
- removeExpiredConfirmations(from: &state)
290
- guard state.confirmations.count < 64 else {
291
- throw TelegramPrivateFileError.tooManyConfirmations
292
- }
293
- let nonce = UUID().uuidString.replacingOccurrences(of: "-", with: "")
294
- state.confirmations.append(ConfirmationState(
295
- nonce: nonce,
296
- chatID: chatID,
297
- senderID: senderID,
298
- command: request.name,
299
- inputJSON: request.inputJSON,
300
- expiresAt: telegramPrivateStoreDate(expiresAt),
301
- raw: [:]
302
- ))
303
- try writeStateUnlocked(state)
304
- return nonce
305
- }
306
- }
307
-
308
- func consumeConfirmation(nonce: String, chatID: Int64, senderID: Int64) throws -> TelegramStoredConfirmation? {
309
- try files.withStateTransaction {
310
- var state = try loadStateUnlocked()
311
- let countBeforeExpiration = state.confirmations.count
312
- removeExpiredConfirmations(from: &state)
313
- guard let index = state.confirmations.firstIndex(where: {
314
- $0.nonce == nonce && $0.chatID == chatID && $0.senderID == senderID
315
- }) else {
316
- if state.confirmations.count != countBeforeExpiration { try writeStateUnlocked(state) }
317
- return nil
318
- }
319
- let confirmation = state.confirmations.remove(at: index)
320
- try writeStateUnlocked(state)
321
- return TelegramStoredConfirmation(request: TelegramTypedCommandRequest(
322
- name: confirmation.command,
323
- inputJSON: confirmation.inputJSON
324
- ))
325
- }
326
- }
327
-
328
- private func validatedToken(_ value: String) throws -> String {
329
- guard value.range(
330
- of: #"^\d{5,20}:[A-Za-z0-9_-]{20,128}$"#,
331
- options: .regularExpression
332
- ) != nil else { throw TelegramPrivateFileError.invalidStoredValue }
333
- return value
334
- }
335
-
336
- private func removeExpiredConfirmations(from state: inout State) {
337
- let current = now()
338
- state.confirmations.removeAll {
339
- guard let expiration = parseTelegramDate($0.expiresAt) else { return true }
340
- return expiration <= current
341
- }
342
- }
343
-
344
- private func loadStateUnlocked() throws -> State {
345
- guard let data = try files.readStateData() else { return State() }
346
- guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any],
347
- root["schema"] as? String == Self.stateSchema,
348
- root.keys.contains("pairing"),
349
- let chatRows = root["chats"] as? [[String: Any]],
350
- let confirmationRows = root["confirmations"] as? [[String: Any]] else {
351
- throw TelegramPrivateFileError.invalidStoredValue
352
- }
353
- let pairing: PairingState?
354
- if root["pairing"] is NSNull {
355
- pairing = nil
356
- } else if let row = root["pairing"] as? [String: Any] {
357
- pairing = try decodePairing(row)
358
- } else {
359
- throw TelegramPrivateFileError.invalidStoredValue
360
- }
361
- let chats = try chatRows.map(decodeChat)
362
- let confirmations = try confirmationRows.map(decodeConfirmation)
363
- let botID: Int64?
364
- if root["bot_id"] == nil || root["bot_id"] is NSNull {
365
- botID = nil
366
- } else if let value = telegramInt64(root["bot_id"]), value > 0 {
367
- botID = value
368
- } else {
369
- throw TelegramPrivateFileError.invalidStoredValue
370
- }
371
- let pollOffset: Int64
372
- if root["poll_offset"] == nil {
373
- pollOffset = 0
374
- } else if let value = telegramInt64(root["poll_offset"]), value >= 0 {
375
- pollOffset = value
376
- } else {
377
- throw TelegramPrivateFileError.invalidStoredValue
378
- }
379
- return State(
380
- botID: botID,
381
- pollOffset: pollOffset,
382
- pairing: pairing,
383
- chats: chats,
384
- confirmations: confirmations,
385
- raw: root
386
- )
387
- }
388
-
389
- private func writeStateUnlocked(_ state: State) throws {
390
- var root = state.raw
391
- root["schema"] = Self.stateSchema
392
- root["bot_id"] = state.botID ?? NSNull()
393
- root["poll_offset"] = state.pollOffset
394
- root["pairing"] = state.pairing.map(encodePairing) ?? NSNull()
395
- root["chats"] = state.chats.map(encodeChat)
396
- root["confirmations"] = state.confirmations.map(encodeConfirmation)
397
- guard JSONSerialization.isValidJSONObject(root) else {
398
- throw TelegramPrivateFileError.invalidStoredValue
399
- }
400
- var data = try JSONSerialization.data(withJSONObject: root, options: [.sortedKeys])
401
- data.append(0x0A)
402
- try files.writeStateData(data)
403
- }
404
-
405
- private func decodePairing(_ row: [String: Any]) throws -> PairingState {
406
- guard let schema = row["schema"] as? String, schema == Self.pairingSchema,
407
- let code = row["code"] as? String,
408
- let expiresAt = row["expires_at"] as? String,
409
- let used = row["used"] as? Bool else {
410
- throw TelegramPrivateFileError.invalidStoredValue
411
- }
412
- return PairingState(schema: schema, code: code, expiresAt: expiresAt, used: used, raw: row)
413
- }
414
-
415
- private func decodeChat(_ row: [String: Any]) throws -> AuthorizedChat {
416
- guard let chatID = telegramInt64(row["chat_id"]),
417
- let senderID = telegramInt64(row["sender_id"]),
418
- chatID > 0,
419
- senderID > 0,
420
- let pairedAt = row["paired_at"] as? String,
421
- row["active"] == nil || row["active"] is Bool else {
422
- throw TelegramPrivateFileError.invalidStoredValue
423
- }
424
- return AuthorizedChat(
425
- chatID: chatID, senderID: senderID, pairedAt: pairedAt,
426
- active: row["active"] as? Bool ?? true, raw: row
427
- )
428
- }
429
-
430
- private func decodeConfirmation(_ row: [String: Any]) throws -> ConfirmationState {
431
- guard let nonce = row["nonce"] as? String,
432
- let chatID = telegramInt64(row["chat_id"]),
433
- let senderID = telegramInt64(row["sender_id"]),
434
- chatID > 0,
435
- senderID > 0,
436
- let command = row["command"] as? String,
437
- let inputJSON = row["input_json"] as? String,
438
- let expiresAt = row["expires_at"] as? String else {
439
- throw TelegramPrivateFileError.invalidStoredValue
440
- }
441
- return ConfirmationState(
442
- nonce: nonce,
443
- chatID: chatID,
444
- senderID: senderID,
445
- command: command,
446
- inputJSON: inputJSON,
447
- expiresAt: expiresAt,
448
- raw: row
449
- )
450
- }
451
-
452
- private func encodePairing(_ pairing: PairingState) -> [String: Any] {
453
- var row = pairing.raw
454
- row["schema"] = Self.pairingSchema
455
- row["code"] = pairing.code
456
- row["expires_at"] = pairing.expiresAt
457
- row["used"] = pairing.used
458
- return row
459
- }
460
-
461
- private func encodeChat(_ chat: AuthorizedChat) -> [String: Any] {
462
- var row = chat.raw
463
- row["chat_id"] = chat.chatID
464
- row["sender_id"] = chat.senderID
465
- row["paired_at"] = chat.pairedAt
466
- row["active"] = chat.active
467
- return row
468
- }
469
-
470
- private func encodeConfirmation(_ confirmation: ConfirmationState) -> [String: Any] {
471
- var row = confirmation.raw
472
- row["nonce"] = confirmation.nonce
473
- row["chat_id"] = confirmation.chatID
474
- row["sender_id"] = confirmation.senderID
475
- row["command"] = confirmation.command
476
- row["input_json"] = confirmation.inputJSON
477
- row["expires_at"] = confirmation.expiresAt
478
- return row
479
- }
480
- }
481
-
482
- private func telegramInt64(_ value: Any?) -> Int64? {
483
- guard let number = value as? NSNumber,
484
- CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
485
- return Int64(number.stringValue)
486
- }
487
-
488
- private func parseTelegramDate(_ value: String) -> Date? {
489
- let fractional = ISO8601DateFormatter()
490
- fractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
491
- return fractional.date(from: value) ?? ISO8601DateFormatter().date(from: value)
492
- }
493
-
494
- private func telegramPrivateStoreDate(_ date: Date) -> String {
495
- let formatter = ISO8601DateFormatter()
496
- formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
497
- return formatter.string(from: date)
498
- }