@camstack/types 1.2.123 → 1.2.126

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.
@@ -1025,6 +1025,36 @@ export interface AddonDeclaration {
1025
1025
  * `ctx.deps.installPythonRequirements(absolutePath)`.
1026
1026
  */
1027
1027
  readonly python?: AddonPythonRequirements;
1028
+ /**
1029
+ * Host binaries this addon needs on PATH. Same declaration site, same
1030
+ * lifecycle position and same idempotence contract as {@link python} —
1031
+ * the runner satisfies them BEFORE `onInitialize()` runs, so a capability
1032
+ * never advertises itself while the binary it wraps is missing.
1033
+ *
1034
+ * The declaration names WHAT is needed (a binary) plus the package name
1035
+ * each host package manager knows it by — a translation table, not a
1036
+ * procedure. HOW to install (`apt-get update && apt-get install -y …`,
1037
+ * `brew install …`) belongs to the framework, so the next addon that needs
1038
+ * a binary does not re-answer the question.
1039
+ *
1040
+ * Example:
1041
+ * ```jsonc
1042
+ * "camstack": {
1043
+ * "addons": [{
1044
+ * "id": "smb-storage",
1045
+ * "systemDependencies": [
1046
+ * { "binary": "smbclient", "packages": { "apt": "smbclient", "brew": "samba" } }
1047
+ * ]
1048
+ * }]
1049
+ * }
1050
+ * ```
1051
+ *
1052
+ * Failure is NOT fatal: the addon still loads and the capability that needs
1053
+ * the binary must refuse loudly on its own surface. A provider that cannot
1054
+ * run must never make its locations read as *empty* — `empty` is deletable
1055
+ * (D293).
1056
+ */
1057
+ readonly systemDependencies?: readonly AddonSystemDependency[];
1028
1058
  /** Default configuration values */
1029
1059
  readonly defaultConfig?: Readonly<Record<string, unknown>>;
1030
1060
  /**
@@ -1046,6 +1076,43 @@ export interface AddonPythonRequirements {
1046
1076
  */
1047
1077
  readonly requirements: string;
1048
1078
  }
1079
+ /**
1080
+ * One host binary an addon needs on PATH. See
1081
+ * `AddonDeclaration.systemDependencies`.
1082
+ *
1083
+ * Deliberately NOT a shell command. A manifest that carried
1084
+ * `"install": "apt-get install -y smbclient"` would be a per-addon script the
1085
+ * framework runs blind — unportable, unverifiable, and a second answer to a
1086
+ * question the Python mechanism already answered once.
1087
+ */
1088
+ export interface AddonSystemDependency {
1089
+ /**
1090
+ * The executable that must resolve on PATH after the step completes. This
1091
+ * is both the presence check (cheap, and what makes the common path a
1092
+ * no-op) and the success check.
1093
+ */
1094
+ readonly binary: string;
1095
+ /**
1096
+ * Package name per host package manager. A manager absent from this map
1097
+ * means "this addon has no way to install the binary on that host" — the
1098
+ * runner logs it and continues rather than guessing that the package shares
1099
+ * the binary's name.
1100
+ */
1101
+ readonly packages: AddonSystemDependencyPackages;
1102
+ /** Operator-facing reason, surfaced in the log line when the install fails. */
1103
+ readonly reason?: string;
1104
+ }
1105
+ /** Package name per supported host package manager. */
1106
+ export interface AddonSystemDependencyPackages {
1107
+ /** Debian / Ubuntu (`apt-get`) — the container base. */
1108
+ readonly apt?: string;
1109
+ /** Alpine (`apk`). */
1110
+ readonly apk?: string;
1111
+ /** Fedora / RHEL (`dnf`). */
1112
+ readonly dnf?: string;
1113
+ /** macOS Homebrew (`brew`) — the desktop build. */
1114
+ readonly brew?: string;
1115
+ }
1049
1116
  export interface AddonPackageManifest {
1050
1117
  /**
1051
1118
  * Human-readable package name for UI display (e.g., "Pipeline", "Core").
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Secret-field discovery over a {@link ConfigUISchema}.
3
+ *
4
+ * A provider already declares its own form. That declaration is the ONLY place
5
+ * that knows which of its config keys hold a credential, so redaction reads it
6
+ * rather than keeping a second, hand-maintained key list somewhere in the
7
+ * orchestrator — a list that would be right on the day it was written and
8
+ * wrong on the day a provider gained a field.
9
+ *
10
+ * The discriminator is deliberately NOT `type === 'password'` alone. That was
11
+ * the first design and it is wrong: the SFTP provider's `privateKey` is a
12
+ * `textarea` holding a full PEM key. Keying off the input widget would have
13
+ * left it on the wire while reporting that secrets were redacted. See
14
+ * `ConfigFieldBase.secret`.
15
+ *
16
+ * The walk takes `unknown` because that is what the caller has: the
17
+ * `storage-provider` cap declares `configSchema: z.unknown()`, so a provider's
18
+ * schema arrives structurally unverified. It narrows defensively at every
19
+ * step and ignores anything it cannot read — an unreadable node contributes no
20
+ * keys, which is the direction that under-reports rather than over-reports.
21
+ * A caller that needs "did I understand this schema at all" asks
22
+ * {@link schemaDeclaresAnyField}, which answers `false` for a shape this
23
+ * cannot read, so "no secrets" and "no schema" stay distinguishable.
24
+ */
25
+ import type { ConfigField } from './config-ui.js';
26
+ /**
27
+ * The value a redacted secret is replaced with on every read surface.
28
+ *
29
+ * It is also the WRITE-BACK token: an `upsertLocation` that sends this value
30
+ * back for a key means "keep what is stored", which is what lets an operator
31
+ * rename a location without retyping its password. A literal an operator could
32
+ * plausibly choose as a real password would turn that convenience into a way
33
+ * to lock yourself out, hence the sentinel shape.
34
+ */
35
+ export declare const REDACTED_SECRET = "__camstack_redacted__";
36
+ /** Is this field's stored value a credential? */
37
+ export declare function isSecretConfigField(field: ConfigField): boolean;
38
+ /**
39
+ * Every config key in `schema` whose value is a secret, including keys nested
40
+ * inside `group` / `sub-tabs` containers.
41
+ */
42
+ export declare function collectSecretConfigKeys(schema: unknown): ReadonlySet<string>;
43
+ /**
44
+ * True when the value is a readable schema declaring at least one field of any
45
+ * kind. Lets a caller tell "this provider has no secrets" from "this provider's
46
+ * schema could not be read", which are the same empty set otherwise.
47
+ */
48
+ export declare function schemaDeclaresAnyField(schema: unknown): boolean;
@@ -124,6 +124,23 @@ export interface ConfigFieldBase {
124
124
  readonly default?: unknown;
125
125
  readonly span?: 1 | 2 | 3 | 4;
126
126
  readonly showWhen?: ConfigCondition;
127
+ /**
128
+ * The field's stored value is a CREDENTIAL and must never leave the process
129
+ * on a read surface. Redaction machinery keys off
130
+ * {@link isSecretConfigField}, which treats `type: 'password'` as implicitly
131
+ * secret — this flag exists for the fields that carry a secret WITHOUT being
132
+ * a password input.
133
+ *
134
+ * The concrete miss it was added for: the SFTP provider's `privateKey` is a
135
+ * `textarea` (a PEM block does not fit a one-line password box) holding a
136
+ * complete SSH private key. A redaction that trusted `type: 'password'`
137
+ * alone would have shipped with that key still on the wire and an assurance
138
+ * that credentials were redacted — worse than no redaction at all.
139
+ *
140
+ * Mark it on any field whose value would compromise something if read by a
141
+ * non-admin session. It costs nothing when the field is already a password.
142
+ */
143
+ readonly secret?: boolean;
127
144
  /** Per-model knob applicability — see {@link ConfigFieldModelApplicability}. */
128
145
  readonly modelApplicability?: ConfigFieldModelApplicability;
129
146
  /**
@@ -91,24 +91,77 @@ export declare const StorageMigrationFootageMoveInputSchema: z.ZodObject<{
91
91
  leaseId: z.ZodString;
92
92
  }, z.core.$strip>;
93
93
  export type StorageMigrationFootageMoveInput = z.infer<typeof StorageMigrationFootageMoveInputSchema>;
94
+ /**
95
+ * What a `relocateMedia` pass DOES. One engine, three passes — never a second
96
+ * mover (the engine already walks both collections with a timestamp cursor and
97
+ * already has a stamp-without-copy path).
98
+ *
99
+ * - `move` — the default and the historical behaviour: event-media and
100
+ * retrain blobs move to `toLocationId` and their rows are
101
+ * stamped. The enrolled gallery is skipped (D197).
102
+ * - `seal` — ROWS ONLY, no bytes. Every row whose `locationId` is NULL is
103
+ * stamped with `toLocationId`. `toLocationId` here is the id the
104
+ * bytes ALREADY sit on — today's `eventMedia` default — because
105
+ * a NULL row means "wherever `eventMedia` points *now*", and the
106
+ * instant a repoint moves that pointer the row reads from the
107
+ * new disk while its bytes are on the old one.
108
+ * - `gallery` — the inverse selection of `move`: ONLY the retention-exempt
109
+ * (enrolled-gallery) rows, which `move` deliberately skips.
110
+ * `galleryMedia` is `cardinality: 'single'`, so this pass can
111
+ * never run beside a live second location: it is stop-the-world
112
+ * by construction, which is acceptable only because the gallery
113
+ * is a few KB per enrolled sample.
114
+ */
115
+ export declare const MediaRelocateModeSchema: z.ZodEnum<{
116
+ move: "move";
117
+ seal: "seal";
118
+ gallery: "gallery";
119
+ }>;
120
+ export type MediaRelocateMode = z.infer<typeof MediaRelocateModeSchema>;
94
121
  export declare const RelocateMediaInputSchema: z.ZodObject<{
95
122
  toLocationId: z.ZodString;
96
123
  throttleMbps: z.ZodOptional<z.ZodNumber>;
124
+ mode: z.ZodOptional<z.ZodEnum<{
125
+ move: "move";
126
+ seal: "seal";
127
+ gallery: "gallery";
128
+ }>>;
97
129
  }, z.core.$strip>;
98
130
  export type RelocateMediaInput = z.infer<typeof RelocateMediaInputSchema>;
131
+ /** How many rows still carry NO `locationId` — the population a repoint would
132
+ * silently re-aim at a disk that does not hold their bytes. Zero is the only
133
+ * value that permits a non-blocking `eventMedia` cutover. */
134
+ export declare const UnstampedEventMediaCountSchema: z.ZodObject<{
135
+ media: z.ZodNumber;
136
+ retrainFrames: z.ZodNumber;
137
+ total: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ export type UnstampedEventMediaCount = z.infer<typeof UnstampedEventMediaCountSchema>;
99
140
  export declare const StorageMigrationMediaMoveInputSchema: z.ZodObject<{
100
141
  toLocationId: z.ZodString;
101
142
  throttleMbps: z.ZodOptional<z.ZodNumber>;
143
+ mode: z.ZodOptional<z.ZodEnum<{
144
+ move: "move";
145
+ seal: "seal";
146
+ gallery: "gallery";
147
+ }>>;
102
148
  leaseId: z.ZodString;
103
149
  }, z.core.$strip>;
104
150
  export type StorageMigrationMediaMoveInput = z.infer<typeof StorageMigrationMediaMoveInputSchema>;
105
- /** The independently selectable logical storage classes. `recordings`
106
- * encompasses the high and mid segment profiles; `recordingsLow` is low
107
- * segments; `eventMedia` is post-analysis blobs. */
151
+ /** The independently selectable logical storage classes — every class
152
+ * `storage.listLocationDeclarations` reports, so an operator never meets a
153
+ * Zod enum error where they should meet an explanation.
154
+ *
155
+ * `recordings` encompasses the high and mid segment profiles; `recordingsLow`
156
+ * is low segments; `eventMedia` is post-analysis blobs; `galleryMedia` is the
157
+ * enrolled gallery; `backups` is the system backup archive. The last two have
158
+ * their own rules — see {@link StorageMigrationFindingCodeSchema}. */
108
159
  export declare const StorageMigrationClassSchema: z.ZodEnum<{
109
160
  recordings: "recordings";
110
161
  recordingsLow: "recordingsLow";
111
162
  eventMedia: "eventMedia";
163
+ backups: "backups";
164
+ galleryMedia: "galleryMedia";
112
165
  }>;
113
166
  export type StorageMigrationClass = z.infer<typeof StorageMigrationClassSchema>;
114
167
  /** A destination is always an existing, fully-qualified location id. The
@@ -118,21 +171,63 @@ export declare const StorageMigrationDestinationsSchema: z.ZodObject<{
118
171
  recordings: z.ZodOptional<z.ZodString>;
119
172
  recordingsLow: z.ZodOptional<z.ZodString>;
120
173
  eventMedia: z.ZodOptional<z.ZodString>;
174
+ backups: z.ZodOptional<z.ZodString>;
175
+ galleryMedia: z.ZodOptional<z.ZodString>;
121
176
  }, z.core.$strip>;
122
177
  export type StorageMigrationDestinations = z.infer<typeof StorageMigrationDestinationsSchema>;
178
+ /**
179
+ * How a migration sequences the cutover against the byte move.
180
+ *
181
+ * - `blocking` — the historical order: pause, move every byte, repoint,
182
+ * resume. Recording is stopped for the whole move. Right
183
+ * for a small or a cold class, and the only legal mode for
184
+ * a `cardinality: 'single'` class.
185
+ * - `nonBlocking` — repoint FIRST, drain behind: seal, pause, repoint,
186
+ * refresh, resume, then move the past with everything
187
+ * running. The pause is three bounded instants (a detach +
188
+ * attach round, a write-gate drain, a lease) instead of one
189
+ * bounded by bytes. 1.09 TB at 7–14 MB/s is thirty hours of
190
+ * stopped recording under `blocking`; the same move is
191
+ * seconds of stopped recording under `nonBlocking`.
192
+ *
193
+ * The mode is on the JOB, not only on the input, because `status` is where an
194
+ * operator finds out which one is running.
195
+ */
196
+ export declare const StorageMigrationModeSchema: z.ZodEnum<{
197
+ blocking: "blocking";
198
+ nonBlocking: "nonBlocking";
199
+ }>;
200
+ export type StorageMigrationMode = z.infer<typeof StorageMigrationModeSchema>;
123
201
  /** Shared input for planning and starting an orchestrated storage migration. */
124
202
  export declare const StorageMigrationInputSchema: z.ZodObject<{
125
203
  destinations: z.ZodObject<{
126
204
  recordings: z.ZodOptional<z.ZodString>;
127
205
  recordingsLow: z.ZodOptional<z.ZodString>;
128
206
  eventMedia: z.ZodOptional<z.ZodString>;
207
+ backups: z.ZodOptional<z.ZodString>;
208
+ galleryMedia: z.ZodOptional<z.ZodString>;
129
209
  }, z.core.$strip>;
130
210
  throttleMbps: z.ZodOptional<z.ZodNumber>;
211
+ mode: z.ZodOptional<z.ZodEnum<{
212
+ blocking: "blocking";
213
+ nonBlocking: "nonBlocking";
214
+ }>>;
131
215
  }, z.core.$strip>;
132
216
  export type StorageMigrationInput = z.infer<typeof StorageMigrationInputSchema>;
133
- /** The durable coordinator state machine. The only phase that changes default
134
- * locations is `repointing`, after every selected mover has completed and been
135
- * verified. */
217
+ /**
218
+ * The durable coordinator state machine.
219
+ *
220
+ * `blocking`:
221
+ * planning → pausing → moving → verifying → repointing → refreshing → resuming → done
222
+ *
223
+ * `nonBlocking`:
224
+ * planning → sealing → pausing → repointing → refreshing → resuming → draining → verifying → done
225
+ *
226
+ * Same phases, different order plus two new ones — not a second mover.
227
+ * `sealing` closes the `eventMedia` NULL-row hole BEFORE anything is paused;
228
+ * `draining` runs the same movers UNLEASED, after every writer is back up.
229
+ * `repointing` is still the only phase that changes a default location.
230
+ */
136
231
  export declare const StorageMigrationPhaseSchema: z.ZodEnum<{
137
232
  moving: "moving";
138
233
  verifying: "verifying";
@@ -140,7 +235,9 @@ export declare const StorageMigrationPhaseSchema: z.ZodEnum<{
140
235
  done: "done";
141
236
  cancelled: "cancelled";
142
237
  planning: "planning";
238
+ sealing: "sealing";
143
239
  pausing: "pausing";
240
+ draining: "draining";
144
241
  repointing: "repointing";
145
242
  refreshing: "refreshing";
146
243
  resuming: "resuming";
@@ -152,11 +249,41 @@ export declare const StorageMigrationParticipantSchema: z.ZodEnum<{
152
249
  analytics: "analytics";
153
250
  }>;
154
251
  export type StorageMigrationParticipant = z.infer<typeof StorageMigrationParticipantSchema>;
252
+ /**
253
+ * The mover's own numbers, folded onto the coordinator's durable move record.
254
+ *
255
+ * The long half of a non-blocking migration is `draining`, and it is measured
256
+ * in hours: 136 885 files at ~4 MB/s is about five of them. Before this shape
257
+ * existed the only place those numbers appeared was a Loki line, so an operator
258
+ * watching the Admin UI saw `phase: draining` and nothing else for a whole
259
+ * afternoon.
260
+ *
261
+ * It is POLLED, never pushed. Events are telemetry and may be dropped
262
+ * (D8/D11), and a dropped progress event is indistinguishable from a stalled
263
+ * mover — which is the exact failure this is meant to end. The coordinator's
264
+ * `waitForMoves` already fetches the whole {@link RelocateJob} on every tick to
265
+ * read `state`; folding the counters costs no extra read and makes the durable
266
+ * record say afterwards how far a move actually got.
267
+ *
268
+ * `filesTotal` is `null` for "no honest denominator" and is never zero-filled:
269
+ * a windowed footage job (`sinceMs`) and a node with no ledger both genuinely
270
+ * cannot say M, and a 0 there would render as "100 % done".
271
+ */
272
+ export declare const StorageMigrationMoveProgressSchema: z.ZodObject<{
273
+ filesMoved: z.ZodNumber;
274
+ filesTotal: z.ZodNullable<z.ZodNumber>;
275
+ bytesMoved: z.ZodNumber;
276
+ startedAt: z.ZodNumber;
277
+ observedAt: z.ZodNumber;
278
+ }, z.core.$strip>;
279
+ export type StorageMigrationMoveProgress = z.infer<typeof StorageMigrationMoveProgressSchema>;
155
280
  export declare const StorageMigrationMoveSchema: z.ZodObject<{
156
281
  storageClass: z.ZodEnum<{
157
282
  recordings: "recordings";
158
283
  recordingsLow: "recordingsLow";
159
284
  eventMedia: "eventMedia";
285
+ backups: "backups";
286
+ galleryMedia: "galleryMedia";
160
287
  }>;
161
288
  fromLocationId: z.ZodString;
162
289
  toLocationId: z.ZodString;
@@ -169,6 +296,13 @@ export declare const StorageMigrationMoveSchema: z.ZodObject<{
169
296
  cancelled: "cancelled";
170
297
  }>>;
171
298
  error: z.ZodNullable<z.ZodString>;
299
+ progress: z.ZodNullable<z.ZodObject<{
300
+ filesMoved: z.ZodNumber;
301
+ filesTotal: z.ZodNullable<z.ZodNumber>;
302
+ bytesMoved: z.ZodNumber;
303
+ startedAt: z.ZodNumber;
304
+ observedAt: z.ZodNumber;
305
+ }, z.core.$strip>>;
172
306
  }, z.core.$strip>;
173
307
  export type StorageMigrationMove = z.infer<typeof StorageMigrationMoveSchema>;
174
308
  export declare const StorageMigrationJobSchema: z.ZodObject<{
@@ -180,15 +314,23 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
180
314
  done: "done";
181
315
  cancelled: "cancelled";
182
316
  planning: "planning";
317
+ sealing: "sealing";
183
318
  pausing: "pausing";
319
+ draining: "draining";
184
320
  repointing: "repointing";
185
321
  refreshing: "refreshing";
186
322
  resuming: "resuming";
187
323
  }>;
324
+ mode: z.ZodEnum<{
325
+ blocking: "blocking";
326
+ nonBlocking: "nonBlocking";
327
+ }>;
188
328
  destinations: z.ZodObject<{
189
329
  recordings: z.ZodOptional<z.ZodString>;
190
330
  recordingsLow: z.ZodOptional<z.ZodString>;
191
331
  eventMedia: z.ZodOptional<z.ZodString>;
332
+ backups: z.ZodOptional<z.ZodString>;
333
+ galleryMedia: z.ZodOptional<z.ZodString>;
192
334
  }, z.core.$strip>;
193
335
  throttleMbps: z.ZodNumber;
194
336
  moves: z.ZodArray<z.ZodObject<{
@@ -196,6 +338,8 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
196
338
  recordings: "recordings";
197
339
  recordingsLow: "recordingsLow";
198
340
  eventMedia: "eventMedia";
341
+ backups: "backups";
342
+ galleryMedia: "galleryMedia";
199
343
  }>;
200
344
  fromLocationId: z.ZodString;
201
345
  toLocationId: z.ZodString;
@@ -208,6 +352,13 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
208
352
  cancelled: "cancelled";
209
353
  }>>;
210
354
  error: z.ZodNullable<z.ZodString>;
355
+ progress: z.ZodNullable<z.ZodObject<{
356
+ filesMoved: z.ZodNumber;
357
+ filesTotal: z.ZodNullable<z.ZodNumber>;
358
+ bytesMoved: z.ZodNumber;
359
+ startedAt: z.ZodNumber;
360
+ observedAt: z.ZodNumber;
361
+ }, z.core.$strip>>;
211
362
  }, z.core.$strip>>;
212
363
  pauseLeaseId: z.ZodNullable<z.ZodString>;
213
364
  pausedParticipants: z.ZodArray<z.ZodEnum<{
@@ -223,20 +374,233 @@ export declare const StorageMigrationJobSchema: z.ZodObject<{
223
374
  error: z.ZodNullable<z.ZodString>;
224
375
  }, z.core.$strip>;
225
376
  export type StorageMigrationJob = z.infer<typeof StorageMigrationJobSchema>;
377
+ /**
378
+ * What the planner NOTICED but did not refuse.
379
+ *
380
+ * A refusal throws — the operator cannot miss it. A finding is the other half:
381
+ * something true about this plan that changes what the operator should expect,
382
+ * surfaced where they read it rather than in a document they will not open.
383
+ *
384
+ * - `sharesDeviceWithSource` — the destination realpath's to the same place as
385
+ * the source. The move will be a row re-stamp, not a byte move, and it buys
386
+ * no redundancy. NOTE: this is PATH identity, not `st_dev` — two distinct
387
+ * directories on one filesystem are NOT detected. See
388
+ * `deviceIdentityUnknown`.
389
+ * - `deviceIdentityUnknown` — a location carries a `nodeId` other than this
390
+ * one (or has no `basePath`), so its realpath cannot be taken here and the
391
+ * same-device question was not answered at all. Stated rather than assumed
392
+ * clear: a check that silently never fires is worse than no check.
393
+ * - `unstampedEventMediaRows` — how many `eventMedia`/retrain rows still carry
394
+ * no `locationId`. Non-zero refuses a `nonBlocking` cutover.
395
+ * - `blockingOnly` — this class is `cardinality: 'single'`, so it can never
396
+ * span two locations and can only ever be moved stop-the-world.
397
+ * - `noMover` — the class is selectable and planned, but no addon owns a mover
398
+ * for it. The migration cannot move its bytes.
399
+ */
400
+ export declare const StorageMigrationFindingCodeSchema: z.ZodEnum<{
401
+ sharesDeviceWithSource: "sharesDeviceWithSource";
402
+ deviceIdentityUnknown: "deviceIdentityUnknown";
403
+ unstampedEventMediaRows: "unstampedEventMediaRows";
404
+ blockingOnly: "blockingOnly";
405
+ noMover: "noMover";
406
+ }>;
407
+ export type StorageMigrationFindingCode = z.infer<typeof StorageMigrationFindingCodeSchema>;
408
+ export declare const StorageMigrationFindingSchema: z.ZodObject<{
409
+ code: z.ZodEnum<{
410
+ sharesDeviceWithSource: "sharesDeviceWithSource";
411
+ deviceIdentityUnknown: "deviceIdentityUnknown";
412
+ unstampedEventMediaRows: "unstampedEventMediaRows";
413
+ blockingOnly: "blockingOnly";
414
+ noMover: "noMover";
415
+ }>;
416
+ storageClass: z.ZodEnum<{
417
+ recordings: "recordings";
418
+ recordingsLow: "recordingsLow";
419
+ eventMedia: "eventMedia";
420
+ backups: "backups";
421
+ galleryMedia: "galleryMedia";
422
+ }>;
423
+ message: z.ZodString;
424
+ }, z.core.$strip>;
425
+ export type StorageMigrationFinding = z.infer<typeof StorageMigrationFindingSchema>;
226
426
  export declare const StorageMigrationPlanSchema: z.ZodObject<{
227
427
  destinations: z.ZodObject<{
228
428
  recordings: z.ZodOptional<z.ZodString>;
229
429
  recordingsLow: z.ZodOptional<z.ZodString>;
230
430
  eventMedia: z.ZodOptional<z.ZodString>;
431
+ backups: z.ZodOptional<z.ZodString>;
432
+ galleryMedia: z.ZodOptional<z.ZodString>;
231
433
  }, z.core.$strip>;
434
+ mode: z.ZodEnum<{
435
+ blocking: "blocking";
436
+ nonBlocking: "nonBlocking";
437
+ }>;
232
438
  moves: z.ZodArray<z.ZodObject<{
233
439
  storageClass: z.ZodEnum<{
234
440
  recordings: "recordings";
235
441
  recordingsLow: "recordingsLow";
236
442
  eventMedia: "eventMedia";
443
+ backups: "backups";
444
+ galleryMedia: "galleryMedia";
237
445
  }>;
238
446
  fromLocationId: z.ZodString;
239
447
  toLocationId: z.ZodString;
240
448
  }, z.core.$strip>>;
449
+ findings: z.ZodArray<z.ZodObject<{
450
+ code: z.ZodEnum<{
451
+ sharesDeviceWithSource: "sharesDeviceWithSource";
452
+ deviceIdentityUnknown: "deviceIdentityUnknown";
453
+ unstampedEventMediaRows: "unstampedEventMediaRows";
454
+ blockingOnly: "blockingOnly";
455
+ noMover: "noMover";
456
+ }>;
457
+ storageClass: z.ZodEnum<{
458
+ recordings: "recordings";
459
+ recordingsLow: "recordingsLow";
460
+ eventMedia: "eventMedia";
461
+ backups: "backups";
462
+ galleryMedia: "galleryMedia";
463
+ }>;
464
+ message: z.ZodString;
465
+ }, z.core.$strip>>;
241
466
  }, z.core.$strip>;
242
467
  export type StorageMigrationPlan = z.infer<typeof StorageMigrationPlanSchema>;
468
+ /**
469
+ * Which single-flight engine owns a class of work.
470
+ *
471
+ * Shared rather than re-declared per consumer: the coordinator lanes its moves
472
+ * by it, and `storageMigration.movers` labels a mover with it so an operator
473
+ * can see *which* engine is busy when a drain refuses to start beside another.
474
+ */
475
+ export declare const StorageMigrationLaneSchema: z.ZodEnum<{
476
+ media: "media";
477
+ footage: "footage";
478
+ }>;
479
+ export type StorageMigrationLane = z.infer<typeof StorageMigrationLaneSchema>;
480
+ /**
481
+ * A mover as it exists RIGHT NOW, whether or not a migration job owns it.
482
+ *
483
+ * The coordinator's job record is the state of record for a migration, and its
484
+ * moves carry {@link StorageMigrationMoveProgress}. But the movers are usable
485
+ * standalone — `recording.relocateFootage` and `pipelineAnalytics.relocateMedia`
486
+ * are both operator-callable, and on 2026-08-29 a five-hour drain was armed that
487
+ * way because no supported UI path existed. A mover armed like that has no job
488
+ * to fold progress into, so it has to be readable on its own or it is invisible.
489
+ *
490
+ * `migrationJobId` is what tells the two apart: `null` means nothing here
491
+ * orchestrated it.
492
+ */
493
+ export declare const StorageMigrationMoverSchema: z.ZodObject<{
494
+ lane: z.ZodEnum<{
495
+ media: "media";
496
+ footage: "footage";
497
+ }>;
498
+ job: z.ZodObject<{
499
+ jobId: z.ZodString;
500
+ state: z.ZodEnum<{
501
+ failed: "failed";
502
+ running: "running";
503
+ queued: "queued";
504
+ done: "done";
505
+ cancelled: "cancelled";
506
+ }>;
507
+ fromLocationId: z.ZodString;
508
+ toLocationId: z.ZodString;
509
+ deviceId: z.ZodNullable<z.ZodNumber>;
510
+ entities: z.ZodArray<z.ZodString>;
511
+ filesMoved: z.ZodNumber;
512
+ bytesMoved: z.ZodNumber;
513
+ filesTotal: z.ZodNullable<z.ZodNumber>;
514
+ startedAt: z.ZodNumber;
515
+ finishedAt: z.ZodNullable<z.ZodNumber>;
516
+ error: z.ZodNullable<z.ZodString>;
517
+ }, z.core.$strip>;
518
+ migrationJobId: z.ZodNullable<z.ZodString>;
519
+ observedAt: z.ZodNumber;
520
+ }, z.core.$strip>;
521
+ export type StorageMigrationMover = z.infer<typeof StorageMigrationMoverSchema>;
522
+ /**
523
+ * What a SOURCE still holds for one storage class — the number that makes a
524
+ * "drain remaining" action honest rather than hopeful.
525
+ *
526
+ * It comes from the archive (`SegmentHourLedger.census` for footage, the media
527
+ * engine's own selection count for media), never from the resident index: a
528
+ * drain sized off `RecordingIndex` is what reported `done` over 80.3 GB it had
529
+ * never been told about (D295).
530
+ *
531
+ * `items`/`bytes` are `null` for "the archive could not be asked", which is
532
+ * deliberately NOT zero: a drain is still offered for an unknown residue,
533
+ * because refusing on an unanswerable read would hide exactly the case an
534
+ * operator needs to act on.
535
+ */
536
+ export declare const StorageMigrationResidueSchema: z.ZodObject<{
537
+ storageClass: z.ZodEnum<{
538
+ recordings: "recordings";
539
+ recordingsLow: "recordingsLow";
540
+ eventMedia: "eventMedia";
541
+ backups: "backups";
542
+ galleryMedia: "galleryMedia";
543
+ }>;
544
+ fromLocationId: z.ZodString;
545
+ toLocationId: z.ZodString;
546
+ items: z.ZodNullable<z.ZodNumber>;
547
+ bytes: z.ZodNullable<z.ZodNumber>;
548
+ }, z.core.$strip>;
549
+ export type StorageMigrationResidue = z.infer<typeof StorageMigrationResidueSchema>;
550
+ /**
551
+ * Run the DRAIN half and nothing else.
552
+ *
553
+ * A migration that reached `done` has already repointed, so `start` correctly
554
+ * refuses its destination ("already the default") — there is nothing left to
555
+ * repoint. But the drain can fail, be cancelled, be interrupted by a restart,
556
+ * or finish against a work list that was a tenth of the archive (D295), and
557
+ * before this there was no supported way to run only that half: the only way
558
+ * through was calling `recording.relocateFootage` by hand over admin tRPC.
559
+ *
560
+ * `drain` NEVER calls `setDefaultLocations`. That is what keeps `start`'s
561
+ * refusal meaningful: the two verbs are disjoint, so nothing here can silently
562
+ * re-repoint a class that is already migrated.
563
+ */
564
+ export declare const StorageMigrationDrainInputSchema: z.ZodObject<{
565
+ classes: z.ZodArray<z.ZodEnum<{
566
+ recordings: "recordings";
567
+ recordingsLow: "recordingsLow";
568
+ eventMedia: "eventMedia";
569
+ backups: "backups";
570
+ galleryMedia: "galleryMedia";
571
+ }>>;
572
+ throttleMbps: z.ZodOptional<z.ZodNumber>;
573
+ }, z.core.$strip>;
574
+ export type StorageMigrationDrainInput = z.infer<typeof StorageMigrationDrainInputSchema>;
575
+ /** What a footage source still holds, asked of the durable hour ledger. */
576
+ export declare const RelocateResidueInputSchema: z.ZodObject<{
577
+ fromLocationId: z.ZodString;
578
+ footageClass: z.ZodOptional<z.ZodEnum<{
579
+ recordings: "recordings";
580
+ recordingsLow: "recordingsLow";
581
+ }>>;
582
+ }, z.core.$strip>;
583
+ export type RelocateResidueInput = z.infer<typeof RelocateResidueInputSchema>;
584
+ /** `null` = the archive could not answer (no ledger on this node, or the
585
+ * aggregate failed). Never conflated with an empty source. */
586
+ export declare const RelocateResidueSchema: z.ZodNullable<z.ZodObject<{
587
+ segments: z.ZodNumber;
588
+ bytes: z.ZodNumber;
589
+ }, z.core.$strip>>;
590
+ export type RelocateResidue = z.infer<typeof RelocateResidueSchema>;
591
+ /** How many rows a media pass would still act on against a given target — the
592
+ * media lane's denominator AND its residue, from ONE derivation so the two can
593
+ * never disagree. `null` = the count could not be taken. */
594
+ export declare const RelocatableMediaCountSchema: z.ZodNullable<z.ZodObject<{
595
+ rows: z.ZodNumber;
596
+ }, z.core.$strip>>;
597
+ export type RelocatableMediaCount = z.infer<typeof RelocatableMediaCountSchema>;
598
+ export declare const RelocatableMediaCountInputSchema: z.ZodObject<{
599
+ toLocationId: z.ZodString;
600
+ mode: z.ZodOptional<z.ZodEnum<{
601
+ move: "move";
602
+ seal: "seal";
603
+ gallery: "gallery";
604
+ }>>;
605
+ }, z.core.$strip>;
606
+ export type RelocatableMediaCountInput = z.infer<typeof RelocatableMediaCountInputSchema>;