@tx5dr/contracts 1.7.11 → 2.0.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 (200) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts +2 -0
  6. package/dist/schema/__tests__/android-operator-audio.schema.test.d.ts.map +1 -0
  7. package/dist/schema/__tests__/android-operator-audio.schema.test.js +27 -0
  8. package/dist/schema/__tests__/android-operator-audio.schema.test.js.map +1 -0
  9. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts +2 -0
  10. package/dist/schema/__tests__/assisted-queue.schema.test.d.ts.map +1 -0
  11. package/dist/schema/__tests__/assisted-queue.schema.test.js +113 -0
  12. package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -0
  13. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts +2 -0
  14. package/dist/schema/__tests__/audio-sidecar.schema.test.d.ts.map +1 -0
  15. package/dist/schema/__tests__/audio-sidecar.schema.test.js +42 -0
  16. package/dist/schema/__tests__/audio-sidecar.schema.test.js.map +1 -0
  17. package/dist/schema/__tests__/audio.schema.test.js +92 -1
  18. package/dist/schema/__tests__/audio.schema.test.js.map +1 -1
  19. package/dist/schema/__tests__/diagnostics.schema.test.d.ts +2 -0
  20. package/dist/schema/__tests__/diagnostics.schema.test.d.ts.map +1 -0
  21. package/dist/schema/__tests__/diagnostics.schema.test.js +31 -0
  22. package/dist/schema/__tests__/diagnostics.schema.test.js.map +1 -0
  23. package/dist/schema/__tests__/logbook-health.schema.test.d.ts +2 -0
  24. package/dist/schema/__tests__/logbook-health.schema.test.d.ts.map +1 -0
  25. package/dist/schema/__tests__/logbook-health.schema.test.js +104 -0
  26. package/dist/schema/__tests__/logbook-health.schema.test.js.map +1 -0
  27. package/dist/schema/__tests__/observability.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/observability.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/observability.schema.test.js +12 -0
  30. package/dist/schema/__tests__/observability.schema.test.js.map +1 -0
  31. package/dist/schema/__tests__/operator.schema.test.js +9 -3
  32. package/dist/schema/__tests__/operator.schema.test.js.map +1 -1
  33. package/dist/schema/__tests__/plugin-market.schema.test.js +43 -1
  34. package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
  35. package/dist/schema/__tests__/radio-power-support.test.js +12 -0
  36. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
  37. package/dist/schema/__tests__/radio-profile.schema.test.d.ts +2 -0
  38. package/dist/schema/__tests__/radio-profile.schema.test.d.ts.map +1 -0
  39. package/dist/schema/__tests__/radio-profile.schema.test.js +36 -0
  40. package/dist/schema/__tests__/radio-profile.schema.test.js.map +1 -0
  41. package/dist/schema/__tests__/radio.schema.test.js +41 -1
  42. package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
  43. package/dist/schema/__tests__/realtime-settings.schema.test.js +2 -1
  44. package/dist/schema/__tests__/realtime-settings.schema.test.js.map +1 -1
  45. package/dist/schema/__tests__/remote-access.schema.test.d.ts +2 -0
  46. package/dist/schema/__tests__/remote-access.schema.test.d.ts.map +1 -0
  47. package/dist/schema/__tests__/remote-access.schema.test.js +18 -0
  48. package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -0
  49. package/dist/schema/android-operator-audio.schema.d.ts +174 -0
  50. package/dist/schema/android-operator-audio.schema.d.ts.map +1 -0
  51. package/dist/schema/android-operator-audio.schema.js +38 -0
  52. package/dist/schema/android-operator-audio.schema.js.map +1 -0
  53. package/dist/schema/api-response.schema.d.ts +268 -44
  54. package/dist/schema/api-response.schema.d.ts.map +1 -1
  55. package/dist/schema/audio-sidecar.schema.d.ts +75 -0
  56. package/dist/schema/audio-sidecar.schema.d.ts.map +1 -1
  57. package/dist/schema/audio-sidecar.schema.js +32 -0
  58. package/dist/schema/audio-sidecar.schema.js.map +1 -1
  59. package/dist/schema/audio.schema.d.ts +4749 -3
  60. package/dist/schema/audio.schema.d.ts.map +1 -1
  61. package/dist/schema/audio.schema.js +101 -2
  62. package/dist/schema/audio.schema.js.map +1 -1
  63. package/dist/schema/auth.schema.d.ts +171 -12
  64. package/dist/schema/auth.schema.d.ts.map +1 -1
  65. package/dist/schema/auth.schema.js +65 -1
  66. package/dist/schema/auth.schema.js.map +1 -1
  67. package/dist/schema/cpu-profile.schema.d.ts +2 -2
  68. package/dist/schema/cw-decoder.schema.d.ts +74 -74
  69. package/dist/schema/cw-keyer.schema.d.ts +20 -14
  70. package/dist/schema/cw-keyer.schema.d.ts.map +1 -1
  71. package/dist/schema/cw-keyer.schema.js +3 -0
  72. package/dist/schema/cw-keyer.schema.js.map +1 -1
  73. package/dist/schema/desktop-https.schema.d.ts +6 -6
  74. package/dist/schema/device-ui.schema.d.ts +6 -6
  75. package/dist/schema/diagnostics.schema.d.ts +163 -0
  76. package/dist/schema/diagnostics.schema.d.ts.map +1 -0
  77. package/dist/schema/diagnostics.schema.js +46 -0
  78. package/dist/schema/diagnostics.schema.js.map +1 -0
  79. package/dist/schema/ft8.schema.d.ts +12 -2
  80. package/dist/schema/ft8.schema.d.ts.map +1 -1
  81. package/dist/schema/ft8.schema.js +8 -0
  82. package/dist/schema/ft8.schema.js.map +1 -1
  83. package/dist/schema/logbook.schema.d.ts +1940 -18
  84. package/dist/schema/logbook.schema.d.ts.map +1 -1
  85. package/dist/schema/logbook.schema.js +158 -1
  86. package/dist/schema/logbook.schema.js.map +1 -1
  87. package/dist/schema/mode.schema.d.ts +2 -6
  88. package/dist/schema/mode.schema.d.ts.map +1 -1
  89. package/dist/schema/mode.schema.js +2 -6
  90. package/dist/schema/mode.schema.js.map +1 -1
  91. package/dist/schema/observability.schema.d.ts +66 -0
  92. package/dist/schema/observability.schema.d.ts.map +1 -0
  93. package/dist/schema/observability.schema.js +20 -0
  94. package/dist/schema/observability.schema.js.map +1 -0
  95. package/dist/schema/operator.schema.d.ts +6 -6
  96. package/dist/schema/operator.schema.d.ts.map +1 -1
  97. package/dist/schema/operator.schema.js +6 -1
  98. package/dist/schema/operator.schema.js.map +1 -1
  99. package/dist/schema/plugin.schema.d.ts +258 -78
  100. package/dist/schema/plugin.schema.d.ts.map +1 -1
  101. package/dist/schema/plugin.schema.js +57 -2
  102. package/dist/schema/plugin.schema.js.map +1 -1
  103. package/dist/schema/process-monitor.schema.d.ts +132 -132
  104. package/dist/schema/pskreporter.schema.d.ts +1 -3
  105. package/dist/schema/pskreporter.schema.d.ts.map +1 -1
  106. package/dist/schema/pskreporter.schema.js +1 -3
  107. package/dist/schema/pskreporter.schema.js.map +1 -1
  108. package/dist/schema/qso.schema.d.ts +7 -0
  109. package/dist/schema/qso.schema.d.ts.map +1 -1
  110. package/dist/schema/qso.schema.js +7 -0
  111. package/dist/schema/qso.schema.js.map +1 -1
  112. package/dist/schema/radio-capability.schema.d.ts +31 -40
  113. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  114. package/dist/schema/radio-capability.schema.js +7 -15
  115. package/dist/schema/radio-capability.schema.js.map +1 -1
  116. package/dist/schema/radio-power-support.d.ts.map +1 -1
  117. package/dist/schema/radio-power-support.js +3 -0
  118. package/dist/schema/radio-power-support.js.map +1 -1
  119. package/dist/schema/radio-power.schema.d.ts +87 -13
  120. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  121. package/dist/schema/radio-power.schema.js +15 -8
  122. package/dist/schema/radio-power.schema.js.map +1 -1
  123. package/dist/schema/radio-profile.schema.d.ts +1240 -97
  124. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  125. package/dist/schema/radio.schema.d.ts +271 -48
  126. package/dist/schema/radio.schema.d.ts.map +1 -1
  127. package/dist/schema/radio.schema.js +44 -5
  128. package/dist/schema/radio.schema.js.map +1 -1
  129. package/dist/schema/realtime.schema.d.ts +60 -52
  130. package/dist/schema/realtime.schema.d.ts.map +1 -1
  131. package/dist/schema/realtime.schema.js +9 -1
  132. package/dist/schema/realtime.schema.js.map +1 -1
  133. package/dist/schema/slot-info.schema.d.ts +15 -9
  134. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  135. package/dist/schema/slot-info.schema.js +15 -9
  136. package/dist/schema/slot-info.schema.js.map +1 -1
  137. package/dist/schema/spectrum.schema.d.ts +12 -12
  138. package/dist/schema/station-info.schema.d.ts +5 -0
  139. package/dist/schema/station-info.schema.d.ts.map +1 -1
  140. package/dist/schema/station-info.schema.js +5 -0
  141. package/dist/schema/station-info.schema.js.map +1 -1
  142. package/dist/schema/system.schema.d.ts +56 -6
  143. package/dist/schema/system.schema.d.ts.map +1 -1
  144. package/dist/schema/system.schema.js +22 -0
  145. package/dist/schema/system.schema.js.map +1 -1
  146. package/dist/schema/transmission.schema.d.ts +1 -7
  147. package/dist/schema/transmission.schema.d.ts.map +1 -1
  148. package/dist/schema/transmission.schema.js +2 -7
  149. package/dist/schema/transmission.schema.js.map +1 -1
  150. package/dist/schema/update.schema.d.ts +3 -0
  151. package/dist/schema/update.schema.d.ts.map +1 -1
  152. package/dist/schema/update.schema.js +1 -0
  153. package/dist/schema/update.schema.js.map +1 -1
  154. package/dist/schema/voice-keyer.schema.d.ts +16 -16
  155. package/dist/schema/websocket.schema.d.ts +3582 -555
  156. package/dist/schema/websocket.schema.d.ts.map +1 -1
  157. package/dist/schema/websocket.schema.js +160 -3
  158. package/dist/schema/websocket.schema.js.map +1 -1
  159. package/package.json +6 -1
  160. package/src/index.ts +3 -0
  161. package/src/schema/__tests__/android-operator-audio.schema.test.ts +31 -0
  162. package/src/schema/__tests__/assisted-queue.schema.test.ts +121 -0
  163. package/src/schema/__tests__/audio-sidecar.schema.test.ts +48 -0
  164. package/src/schema/__tests__/audio.schema.test.ts +102 -0
  165. package/src/schema/__tests__/diagnostics.schema.test.ts +32 -0
  166. package/src/schema/__tests__/logbook-health.schema.test.ts +117 -0
  167. package/src/schema/__tests__/observability.schema.test.ts +15 -0
  168. package/src/schema/__tests__/operator.schema.test.ts +11 -3
  169. package/src/schema/__tests__/plugin-market.schema.test.ts +49 -0
  170. package/src/schema/__tests__/radio-power-support.test.ts +14 -0
  171. package/src/schema/__tests__/radio-profile.schema.test.ts +44 -0
  172. package/src/schema/__tests__/radio.schema.test.ts +49 -1
  173. package/src/schema/__tests__/realtime-settings.schema.test.ts +2 -1
  174. package/src/schema/__tests__/remote-access.schema.test.ts +22 -0
  175. package/src/schema/android-operator-audio.schema.ts +49 -0
  176. package/src/schema/audio-sidecar.schema.ts +35 -0
  177. package/src/schema/audio.schema.ts +115 -2
  178. package/src/schema/auth.schema.ts +79 -1
  179. package/src/schema/cw-keyer.schema.ts +5 -0
  180. package/src/schema/diagnostics.schema.ts +55 -0
  181. package/src/schema/ft8.schema.ts +8 -0
  182. package/src/schema/logbook.schema.ts +204 -1
  183. package/src/schema/mode.schema.ts +2 -6
  184. package/src/schema/observability.schema.ts +26 -0
  185. package/src/schema/operator.schema.ts +7 -1
  186. package/src/schema/plugin.schema.ts +67 -2
  187. package/src/schema/pskreporter.schema.ts +1 -3
  188. package/src/schema/qso.schema.ts +7 -0
  189. package/src/schema/radio-capability.schema.ts +7 -15
  190. package/src/schema/radio-power-support.ts +3 -0
  191. package/src/schema/radio-power.schema.ts +15 -8
  192. package/src/schema/radio.schema.ts +55 -5
  193. package/src/schema/realtime.schema.ts +9 -1
  194. package/src/schema/slot-info.schema.ts +16 -10
  195. package/src/schema/station-info.schema.ts +5 -0
  196. package/src/schema/system.schema.ts +29 -0
  197. package/src/schema/transmission.schema.ts +2 -7
  198. package/src/schema/update.schema.ts +1 -0
  199. package/src/schema/websocket.schema.ts +223 -6
  200. package/test/radio-capability-schema.test.ts +44 -0
@@ -82,6 +82,19 @@ export const LoginResponseSchema = z.object({
82
82
 
83
83
  export type LoginResponse = z.infer<typeof LoginResponseSchema>;
84
84
 
85
+ export const BrowserLoginCodeResponseSchema = z.object({
86
+ code: z.string().min(1),
87
+ expiresAt: z.number(),
88
+ });
89
+
90
+ export type BrowserLoginCodeResponse = z.infer<typeof BrowserLoginCodeResponseSchema>;
91
+
92
+ export const ExchangeBrowserLoginCodeRequestSchema = z.object({
93
+ code: z.string().min(1),
94
+ });
95
+
96
+ export type ExchangeBrowserLoginCodeRequest = z.infer<typeof ExchangeBrowserLoginCodeRequestSchema>;
97
+
85
98
  // ===== Token 管理(Admin API) =====
86
99
 
87
100
  export const LoginCredentialSummarySchema = z.object({
@@ -211,11 +224,76 @@ export const UpdateAuthConfigRequestSchema = z.object({
211
224
 
212
225
  export type UpdateAuthConfigRequest = z.infer<typeof UpdateAuthConfigRequestSchema>;
213
226
 
227
+ export const RemoteAccessPresetSchema = z.enum(['local', 'lan', 'public']);
228
+ export type RemoteAccessPreset = z.infer<typeof RemoteAccessPresetSchema>;
229
+
230
+ export function isPrivateRemoteAccessHostname(hostname: string): boolean {
231
+ if (['localhost', '127.0.0.1', '::1'].includes(hostname)) return true;
232
+ if (hostname.includes(':')) {
233
+ const normalized = hostname.toLowerCase();
234
+ return normalized.startsWith('fc') || normalized.startsWith('fd') || normalized.startsWith('fe8')
235
+ || normalized.startsWith('fe9') || normalized.startsWith('fea') || normalized.startsWith('feb');
236
+ }
237
+ const parts = hostname.split('.').map(Number);
238
+ if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return false;
239
+ return parts[0] === 10
240
+ || (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
241
+ || (parts[0] === 192 && parts[1] === 168)
242
+ || (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127)
243
+ || (parts[0] === 169 && parts[1] === 254);
244
+ }
245
+
246
+ export function normalizeRemoteAccessOrigin(value: string): string | null {
247
+ try {
248
+ const parsed = new URL(value.trim());
249
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
250
+ if (parsed.username || parsed.password || parsed.pathname !== '/' || parsed.search || parsed.hash) return null;
251
+ return parsed.origin;
252
+ } catch {
253
+ return null;
254
+ }
255
+ }
256
+
257
+ export function isSecureRemoteAccessOrigin(value: string): boolean {
258
+ const normalized = normalizeRemoteAccessOrigin(value);
259
+ if (!normalized) return false;
260
+ const parsed = new URL(normalized);
261
+ return parsed.protocol === 'https:' || isPrivateRemoteAccessHostname(parsed.hostname);
262
+ }
263
+
264
+ export const RemoteAccessSecurityConfigSchema = z.object({
265
+ preset: RemoteAccessPresetSchema.default('lan'),
266
+ allowedOrigins: z.array(z.string().url().refine(
267
+ value => normalizeRemoteAccessOrigin(value) !== null,
268
+ 'Expected a complete HTTP(S) origin without a path',
269
+ )).max(32).default([]),
270
+ maxConnections: z.number().int().min(1).max(512).default(32),
271
+ maxConnectionsPerIp: z.number().int().min(1).max(128).default(16),
272
+ maxPendingAuth: z.number().int().min(1).max(128).default(32),
273
+ authTimeoutMs: z.number().int().min(3_000).max(60_000).default(10_000),
274
+ handshakeTimeoutMs: z.number().int().min(3_000).max(60_000).default(10_000),
275
+ });
276
+ export type RemoteAccessSecurityConfig = z.infer<typeof RemoteAccessSecurityConfigSchema>;
277
+
278
+ export const UpdateRemoteAccessSecurityRequestSchema = RemoteAccessSecurityConfigSchema.partial().extend({
279
+ allowPublicViewing: z.boolean().optional(),
280
+ });
281
+ export type UpdateRemoteAccessSecurityRequest = z.infer<typeof UpdateRemoteAccessSecurityRequestSchema>;
282
+
283
+ export const RemoteAccessSecurityStatusSchema = RemoteAccessSecurityConfigSchema.extend({
284
+ allowPublicViewing: z.boolean(),
285
+ activeConnections: z.number().int().nonnegative(),
286
+ pendingConnections: z.number().int().nonnegative(),
287
+ });
288
+ export type RemoteAccessSecurityStatus = z.infer<typeof RemoteAccessSecurityStatusSchema>;
289
+
214
290
  // ===== 认证配置(持久化到 auth.json) =====
215
291
 
216
292
  export const AuthConfigSchema = z.object({
217
293
  enabled: z.boolean().default(true),
218
- allowPublicViewing: z.boolean().default(true),
294
+ // Existing installations persist their explicit value; new installations opt in.
295
+ allowPublicViewing: z.boolean().default(false),
296
+ remoteAccess: RemoteAccessSecurityConfigSchema.default({}),
219
297
  jwtSecret: z.string().optional(),
220
298
  jwtExpiresInSeconds: z.number().default(7 * 24 * 3600), // 7 days
221
299
  tokens: z.array(AuthTokenSchema).default([]),
@@ -6,6 +6,9 @@ import { z } from 'zod';
6
6
  export const CWKeyerBackendSchema = z.enum(['cat', 'serial']);
7
7
  export type CWKeyerBackend = z.infer<typeof CWKeyerBackendSchema>;
8
8
 
9
+ export const CWKeyActiveLevelSchema = z.enum(['high', 'low']);
10
+ export type CWKeyActiveLevel = z.infer<typeof CWKeyActiveLevelSchema>;
11
+
9
12
  export const CWKeyerConfigSchema = z.object({
10
13
  /** CW 发报后端:cat=Hamlib 整报文,serial=DTR/RTS 时序键控 */
11
14
  backend: CWKeyerBackendSchema.default('cat'),
@@ -13,6 +16,8 @@ export const CWKeyerConfigSchema = z.object({
13
16
  keyPort: z.string(),
14
17
  /** CW 键控引脚类型 */
15
18
  keyMethod: z.enum(['dtr', 'rts']),
19
+ /** CW 键控有效电平:high=高电平按下,low=低电平按下 */
20
+ keyActiveLevel: CWKeyActiveLevelSchema.default('high'),
16
21
  /** 莫尔斯码速度 (WPM, 5-60) */
17
22
  wpm: z.number().int().min(5).max(60).default(20),
18
23
  });
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+
3
+ export const DiagnosticLogSourceIdSchema = z.enum(['server', 'electron-main', 'client-tools']);
4
+ export type DiagnosticLogSourceId = z.infer<typeof DiagnosticLogSourceIdSchema>;
5
+
6
+ export const DiagnosticLogSourceSchema = z.object({
7
+ id: DiagnosticLogSourceIdSchema,
8
+ fileName: z.string().min(1),
9
+ fileCount: z.number().int().positive(),
10
+ totalBytes: z.number().int().nonnegative(),
11
+ availableFromMs: z.number().int().positive().nullable(),
12
+ availableToMs: z.number().int().positive().nullable(),
13
+ });
14
+ export type DiagnosticLogSource = z.infer<typeof DiagnosticLogSourceSchema>;
15
+
16
+ export const DiagnosticLogSourcesResponseSchema = z.object({
17
+ sources: z.array(DiagnosticLogSourceSchema),
18
+ limits: z.object({
19
+ maxRangeMs: z.number().int().positive(),
20
+ maxUncompressedBytes: z.number().int().positive(),
21
+ maxCompressedBytes: z.number().int().positive(),
22
+ feedbackMaxCharacters: z.number().int().positive(),
23
+ }),
24
+ });
25
+ export type DiagnosticLogSourcesResponse = z.infer<typeof DiagnosticLogSourcesResponseSchema>;
26
+
27
+ export const CreateDiagnosticUploadRequestSchema = z.object({
28
+ sourceId: DiagnosticLogSourceIdSchema,
29
+ fromMs: z.number().int().positive(),
30
+ toMs: z.number().int().positive(),
31
+ feedback: z.string().trim().max(2000).optional(),
32
+ }).strict().superRefine((value, context) => {
33
+ if (value.toMs <= value.fromMs) {
34
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['toMs'], message: 'toMs must be later than fromMs' });
35
+ }
36
+ if (value.toMs - value.fromMs > 7 * 24 * 60 * 60 * 1000) {
37
+ context.addIssue({ code: z.ZodIssueCode.custom, path: ['toMs'], message: 'time range exceeds seven days' });
38
+ }
39
+ });
40
+ export type CreateDiagnosticUploadRequest = z.infer<typeof CreateDiagnosticUploadRequestSchema>;
41
+
42
+ export const DiagnosticUploadReceiptSchema = z.object({
43
+ uploadId: z.string().uuid(),
44
+ sourceId: DiagnosticLogSourceIdSchema,
45
+ requestedFromMs: z.number().int().positive(),
46
+ requestedToMs: z.number().int().positive(),
47
+ includedFromMs: z.number().int().positive(),
48
+ includedToMs: z.number().int().positive(),
49
+ lineCount: z.number().int().positive(),
50
+ uncompressedBytes: z.number().int().positive(),
51
+ compressedBytes: z.number().int().positive(),
52
+ acceptedAt: z.string().datetime(),
53
+ retainedUntil: z.string().datetime(),
54
+ });
55
+ export type DiagnosticUploadReceipt = z.infer<typeof DiagnosticUploadReceiptSchema>;
@@ -276,6 +276,9 @@ export const FT8MessageSchema = z.discriminatedUnion('type', [
276
276
  * This is the main plugin-facing FT8 payload. It combines RF metrics from the
277
277
  * decoder (`snr`, `dt`, `df`), the original unparsed text (`rawMessage`), the
278
278
  * normalized semantic form (`message`) and optional logbook enrichment.
279
+ * `snr` is dB, `dt` is seconds from the expected decode timing, `df` is audio
280
+ * offset in hertz, and `timestamp` is Unix epoch milliseconds. `slotId` ties
281
+ * the decode back to its `SlotInfo`/`SlotPack`.
279
282
  */
280
283
  export const ParsedFT8MessageSchema = z.object({
281
284
  snr: z.number(),
@@ -285,6 +288,11 @@ export const ParsedFT8MessageSchema = z.object({
285
288
  message: FT8MessageSchema,
286
289
  slotId: z.string(),
287
290
  timestamp: z.number(),
291
+ /**
292
+ * 标记该消息为 WSJT-X 部分解码(含 `<...>` 未解码呼号占位符)。
293
+ * 部分解码消息不得作为自动呼叫/回复目标,仅供监控与防御使用。
294
+ */
295
+ isPartialDecode: z.boolean().optional(),
288
296
  logbookAnalysis: LogbookAnalysisSchema.optional(),
289
297
  });
290
298
 
@@ -1,4 +1,49 @@
1
1
  import { z } from 'zod';
2
+ import { QSORecordSchema } from './qso.schema.js';
3
+
4
+ export const LOGBOOK_OPERATION_ERROR_CODES = [
5
+ 'LOGBOOK_LOADING',
6
+ 'LOGBOOK_READ_ONLY',
7
+ 'LOGBOOK_UNAVAILABLE',
8
+ 'LOGBOOK_WRITE_FAILED',
9
+ 'LOGBOOK_WRITE_STATE_UNCERTAIN',
10
+ 'LOGBOOK_UNSAVED_QSO_NOT_FOUND',
11
+ 'LOGBOOK_BACKUP_FAILED',
12
+ 'LOGBOOK_BACKUP_UNAVAILABLE',
13
+ 'LOGBOOK_BACKUP_CHANGED',
14
+ 'LOGBOOK_MAINTENANCE',
15
+ 'LOGBOOK_REVISION_MISMATCH',
16
+ 'LOGBOOK_RESTORE_PRECONDITION_FAILED',
17
+ 'LOGBOOK_PRECONDITION_REQUIRED',
18
+ 'LOGBOOK_IDEMPOTENCY_CONFLICT',
19
+ ] as const;
20
+
21
+ export const LogbookOperationErrorCodeSchema = z.enum(LOGBOOK_OPERATION_ERROR_CODES);
22
+
23
+ export const LogbookHealthStateSchema = z.enum([
24
+ 'loading',
25
+ 'healthy',
26
+ 'degraded',
27
+ 'read_only',
28
+ 'unavailable',
29
+ ]);
30
+
31
+ export const LogbookHealthIssueSchema = z.object({
32
+ code: z.string(),
33
+ message: z.string(),
34
+ affectedRecords: z.number().int().nonnegative().optional(),
35
+ affectedBytes: z.number().int().nonnegative().optional(),
36
+ recoveryFileName: z.string().optional(),
37
+ occurredAt: z.number(),
38
+ });
39
+
40
+ export const LogbookHealthSchema = z.object({
41
+ state: LogbookHealthStateSchema,
42
+ readable: z.boolean(),
43
+ writable: z.boolean(),
44
+ issues: z.array(LogbookHealthIssueSchema),
45
+ updatedAt: z.number(),
46
+ });
2
47
 
3
48
  export const DxccCountSummarySchema = z.object({
4
49
  current: z.number().default(0),
@@ -27,10 +72,12 @@ export const LogBookInfoSchema = z.object({
27
72
  id: z.string(),
28
73
  name: z.string(),
29
74
  description: z.string().optional(),
30
- filePath: z.string(),
75
+ fileName: z.string().min(1).max(255),
76
+ storageKind: z.enum(['managed', 'custom']),
31
77
  createdAt: z.number(),
32
78
  lastUsed: z.number(),
33
79
  isActive: z.boolean(),
80
+ health: LogbookHealthSchema,
34
81
  });
35
82
 
36
83
  /**
@@ -102,6 +149,141 @@ export const LogBookActionResponseSchema = z.object({
102
149
  data: LogBookInfoSchema.optional(),
103
150
  });
104
151
 
152
+ export const LogbookRecoveryRetryResponseSchema = z.object({
153
+ success: z.boolean(),
154
+ data: z.object({
155
+ logBookId: z.string(),
156
+ health: LogbookHealthSchema,
157
+ }),
158
+ });
159
+
160
+ const LogbookRevisionSchema = z.string()
161
+ .min(1)
162
+ .max(256)
163
+ .regex(/^[A-Za-z0-9._:"-]+$/);
164
+ const LogbookIdempotencyKeySchema = z.string()
165
+ .min(8)
166
+ .max(128)
167
+ .regex(/^[A-Za-z0-9._:-]+$/);
168
+ const LogbookRecoveryTokenSchema = z.string()
169
+ .min(8)
170
+ .max(128)
171
+ .regex(/^[A-Za-z0-9._:-]+$/);
172
+ const UnsavedQsoAttemptIdSchema = z.string()
173
+ .min(8)
174
+ .max(128)
175
+ .regex(/^[A-Za-z0-9._:-]+$/);
176
+
177
+ export const LogbookUnsavedQsoSummarySchema = z.object({
178
+ attemptId: UnsavedQsoAttemptIdSchema,
179
+ operatorId: z.string().min(1).max(128),
180
+ createdAt: z.number(),
181
+ callsign: z.string().max(64),
182
+ mode: z.string().max(32),
183
+ });
184
+
185
+ export const LogbookBackupArtifactSchema = z.object({
186
+ createdAt: z.number(),
187
+ size: z.number().int().nonnegative(),
188
+ recordCount: z.number().int().nonnegative().optional(),
189
+ opaqueRecordCount: z.number().int().nonnegative().optional(),
190
+ });
191
+
192
+ export const LogbookBackupOperationSchema = z.object({
193
+ id: z.string().min(1).max(128),
194
+ kind: z.enum(['backup', 'restore-prepare', 'restore']),
195
+ state: z.enum(['queued', 'running', 'succeeded', 'failed']),
196
+ phase: z.string().min(1).max(128),
197
+ processedBytes: z.number().int().nonnegative().optional(),
198
+ totalBytes: z.number().int().nonnegative().optional(),
199
+ errorCode: z.string().min(1).max(128).optional(),
200
+ });
201
+
202
+ export const LogbookBackupStatusSchema = z.object({
203
+ logBookId: z.string(),
204
+ revision: LogbookRevisionSchema,
205
+ mainHealth: LogbookHealthSchema,
206
+ dirty: z.boolean(),
207
+ pendingMutations: z.number().int().nonnegative(),
208
+ latest: LogbookBackupArtifactSchema.optional(),
209
+ preRestore: LogbookBackupArtifactSchema.optional(),
210
+ operation: LogbookBackupOperationSchema.optional(),
211
+ unsaved: z.array(LogbookUnsavedQsoSummarySchema).optional(),
212
+ capabilities: z.object({
213
+ canCreate: z.boolean(),
214
+ canDownload: z.boolean(),
215
+ canRestore: z.boolean(),
216
+ canDownloadPreRestore: z.boolean(),
217
+ }),
218
+ error: z.object({
219
+ code: z.string().min(1).max(128),
220
+ message: z.string(),
221
+ }).optional(),
222
+ });
223
+
224
+ export const LogbookBackupStatusResponseSchema = z.object({
225
+ success: z.literal(true),
226
+ data: LogbookBackupStatusSchema,
227
+ });
228
+
229
+ export const CreateLogbookBackupRequestSchema = z.object({
230
+ }).strict();
231
+
232
+ export const PrepareLogbookRestoreRequestSchema = z.object({
233
+ }).strict();
234
+
235
+ export const RestoreLogbookRequestSchema = z.object({
236
+ preflightToken: LogbookRecoveryTokenSchema,
237
+ confirmation: z.string().min(1).max(128),
238
+ }).strict();
239
+
240
+ export const LogbookConditionalMutationHeadersSchema = z.object({
241
+ revision: LogbookRevisionSchema,
242
+ idempotencyKey: LogbookIdempotencyKeySchema,
243
+ }).strict();
244
+
245
+ export const LogbookIdempotentMutationHeadersSchema = z.object({
246
+ idempotencyKey: LogbookIdempotencyKeySchema,
247
+ }).strict();
248
+
249
+ export const LogbookRestoreFileSummarySchema = z.object({
250
+ size: z.number().int().nonnegative(),
251
+ recordCount: z.number().int().nonnegative(),
252
+ opaqueRecordCount: z.number().int().nonnegative(),
253
+ incompleteTail: z.boolean(),
254
+ issueCount: z.number().int().nonnegative(),
255
+ });
256
+
257
+ export const LogbookRestorePreflightSchema = z.object({
258
+ preflightToken: LogbookRecoveryTokenSchema,
259
+ expiresAt: z.number(),
260
+ revision: LogbookRevisionSchema,
261
+ main: LogbookRestoreFileSummarySchema,
262
+ backup: LogbookRestoreFileSummarySchema,
263
+ recordDelta: z.number().int(),
264
+ estimatedLoss: z.number().int().nonnegative(),
265
+ highRisk: z.boolean(),
266
+ });
267
+
268
+ export const LogbookRestorePreflightResponseSchema = z.object({
269
+ success: z.literal(true),
270
+ data: LogbookRestorePreflightSchema,
271
+ });
272
+
273
+ export const LogbookUnsavedQsoRetryResponseSchema = z.object({
274
+ success: z.literal(true),
275
+ data: QSORecordSchema,
276
+ });
277
+
278
+ export const LogbookUnsavedQsoDiscardResponseSchema = z.object({
279
+ success: z.literal(true),
280
+ data: z.object({ attemptId: UnsavedQsoAttemptIdSchema }),
281
+ });
282
+
283
+ export const LogbookUnsavedQsoAttemptParamsSchema = z.object({
284
+ attemptId: UnsavedQsoAttemptIdSchema,
285
+ }).strict();
286
+
105
287
  /**
106
288
  * 日志本QSO查询选项Schema
107
289
  */
@@ -293,6 +475,10 @@ export const QSOActionResponseSchema = z.object({
293
475
  // ========== 类型导出 ==========
294
476
 
295
477
  export type LogBookInfo = z.infer<typeof LogBookInfoSchema>;
478
+ export type LogbookOperationErrorCode = z.infer<typeof LogbookOperationErrorCodeSchema>;
479
+ export type LogbookHealthState = z.infer<typeof LogbookHealthStateSchema>;
480
+ export type LogbookHealthIssue = z.infer<typeof LogbookHealthIssueSchema>;
481
+ export type LogbookHealth = z.infer<typeof LogbookHealthSchema>;
296
482
  export type CreateLogBookRequest = z.infer<typeof CreateLogBookRequestSchema>;
297
483
  export type UpdateLogBookRequest = z.infer<typeof UpdateLogBookRequestSchema>;
298
484
  export type ConnectOperatorToLogBookRequest = z.infer<typeof ConnectOperatorToLogBookRequestSchema>;
@@ -301,6 +487,23 @@ export type LogBookDxccSummary = z.infer<typeof LogBookDxccSummarySchema>;
301
487
  export type LogBookListResponse = z.infer<typeof LogBookListResponseSchema>;
302
488
  export type LogBookDetailResponse = z.infer<typeof LogBookDetailResponseSchema>;
303
489
  export type LogBookActionResponse = z.infer<typeof LogBookActionResponseSchema>;
490
+ export type LogbookRecoveryRetryResponse = z.infer<typeof LogbookRecoveryRetryResponseSchema>;
491
+ export type LogbookUnsavedQsoSummary = z.infer<typeof LogbookUnsavedQsoSummarySchema>;
492
+ export type LogbookBackupArtifact = z.infer<typeof LogbookBackupArtifactSchema>;
493
+ export type LogbookBackupOperation = z.infer<typeof LogbookBackupOperationSchema>;
494
+ export type LogbookBackupStatus = z.infer<typeof LogbookBackupStatusSchema>;
495
+ export type LogbookBackupStatusResponse = z.infer<typeof LogbookBackupStatusResponseSchema>;
496
+ export type CreateLogbookBackupRequest = z.infer<typeof CreateLogbookBackupRequestSchema>;
497
+ export type PrepareLogbookRestoreRequest = z.infer<typeof PrepareLogbookRestoreRequestSchema>;
498
+ export type RestoreLogbookRequest = z.infer<typeof RestoreLogbookRequestSchema>;
499
+ export type LogbookConditionalMutationHeaders = z.infer<typeof LogbookConditionalMutationHeadersSchema>;
500
+ export type LogbookIdempotentMutationHeaders = z.infer<typeof LogbookIdempotentMutationHeadersSchema>;
501
+ export type LogbookRestoreFileSummary = z.infer<typeof LogbookRestoreFileSummarySchema>;
502
+ export type LogbookRestorePreflight = z.infer<typeof LogbookRestorePreflightSchema>;
503
+ export type LogbookRestorePreflightResponse = z.infer<typeof LogbookRestorePreflightResponseSchema>;
504
+ export type LogbookUnsavedQsoRetryResponse = z.infer<typeof LogbookUnsavedQsoRetryResponseSchema>;
505
+ export type LogbookUnsavedQsoDiscardResponse = z.infer<typeof LogbookUnsavedQsoDiscardResponseSchema>;
506
+ export type LogbookUnsavedQsoAttemptParams = z.infer<typeof LogbookUnsavedQsoAttemptParamsSchema>;
304
507
  export type LogBookQSOQueryOptions = z.infer<typeof LogBookQSOQueryOptionsSchema>;
305
508
  export type LogBookRecentGlobeQuery = z.infer<typeof LogBookRecentGlobeQuerySchema>;
306
509
  export type LogBookRecentGlobeHomeSource = z.infer<typeof LogBookRecentGlobeHomeSourceSchema>;
@@ -1,8 +1,6 @@
1
1
  import { z } from 'zod';
2
2
 
3
- /**
4
- * 模式描述符 - 定义 FT8/FT4 等模式的时序参数
5
- */
3
+ /** Timing, decoder and display parameters for a digital mode such as FT8 or FT4. */
6
4
  export const ModeDescriptorSchema = z.object({
7
5
  /** 模式名称,如 "FT8", "FT4" */
8
6
  name: z.string(),
@@ -109,9 +107,7 @@ export const FT4_WINDOW_PRESETS: Record<string, number[]> = {
109
107
  lightweight: [0], // 1 轮:仅末端
110
108
  };
111
109
 
112
- /**
113
- * 解码窗口设置 Schema
114
- */
110
+ /** Per-mode decode-window presets and optional custom timing windows. */
115
111
  export const DecodeWindowSettingsSchema = z.object({
116
112
  ft8: z.object({
117
113
  preset: z.enum(['maximum', 'balanced', 'lightweight', 'minimum', 'custom']).default('balanced'),
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+
3
+ export const OBSERVABILITY_NOTICE_VERSION = 1;
4
+
5
+ export const ObservabilitySettingsSchema = z.object({
6
+ enabled: z.boolean(),
7
+ noticeVersion: z.number().int().nonnegative(),
8
+ });
9
+ export type ObservabilitySettings = z.infer<typeof ObservabilitySettingsSchema>;
10
+
11
+ export const UpdateObservabilitySettingsSchema = z.object({
12
+ enabled: z.boolean(),
13
+ noticeVersion: z.literal(OBSERVABILITY_NOTICE_VERSION),
14
+ });
15
+ export type UpdateObservabilitySettingsRequest = z.infer<typeof UpdateObservabilitySettingsSchema>;
16
+
17
+ export const ObservabilityStatusSchema = z.object({
18
+ settings: ObservabilitySettingsSchema,
19
+ effectiveEnabled: z.boolean(),
20
+ noticeRequired: z.boolean(),
21
+ endpointConfigured: z.boolean(),
22
+ queueDepth: z.number().int().nonnegative(),
23
+ lastSentAt: z.number().int().nonnegative().nullable(),
24
+ lastError: z.string().nullable(),
25
+ });
26
+ export type ObservabilityStatus = z.infer<typeof ObservabilityStatusSchema>;
@@ -1,18 +1,24 @@
1
1
  import { z } from 'zod';
2
2
  import { ModeDescriptorSchema } from './mode.schema.js';
3
3
  import { sanitizeCallsignInput } from '../utils/callsign.js';
4
+ import { sanitizeGridInput } from '../utils/grid.js';
4
5
 
5
6
  const OperatorCallsignSchema = z
6
7
  .string()
7
8
  .transform(sanitizeCallsignInput)
8
9
  .pipe(z.string().min(1, '呼号不能为空').max(10, '呼号不能超过10个字符'));
9
10
 
11
+ const OperatorGridSchema = z
12
+ .string()
13
+ .transform(sanitizeGridInput)
14
+ .pipe(z.string().min(4, '网格坐标至少4位').max(8, '网格坐标不能超过8位'));
15
+
10
16
  // 操作员配置 Schema (重命名以避免冲突)
11
17
  export const RadioOperatorConfigSchema = z.object({
12
18
  id: z.string().min(1, '操作员ID不能为空'),
13
19
  createdByTokenId: z.string().optional(), // 创建者 Token ID(仅用于审计,不参与权限判断)
14
20
  myCallsign: OperatorCallsignSchema,
15
- myGrid: z.string().min(4, '网格坐标至少4位').max(8, '网格坐标不能超过8位').optional(),
21
+ myGrid: OperatorGridSchema.optional(),
16
22
  frequency: z.number().min(0).max(1000000000, '频率必须在0-1GHz之间').default(1000), // 音频偏移频率(Hz),默认1000Hz,创建时自动分配不重复值
17
23
  transmitCycles: z.array(z.number().min(0).max(1)).default([0]), // 0=偶数周期,1=奇数周期
18
24
  mode: ModeDescriptorSchema.optional(),