@camstack/addon-terminal 0.1.46 → 0.1.48

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 (3) hide show
  1. package/dist/addon.js +154 -19
  2. package/dist/addon.mjs +154 -19
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -8138,18 +8138,61 @@ var RelocateFootageInputSchema = object({
8138
8138
  * `RecordingConfig.enabled` or camera wrapper bindings. */
8139
8139
  var StorageMigrationLeaseInputSchema = object({ leaseId: string().min(1) });
8140
8140
  var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: string().min(1) });
8141
+ /**
8142
+ * What a `relocateMedia` pass DOES. One engine, three passes — never a second
8143
+ * mover (the engine already walks both collections with a timestamp cursor and
8144
+ * already has a stamp-without-copy path).
8145
+ *
8146
+ * - `move` — the default and the historical behaviour: event-media and
8147
+ * retrain blobs move to `toLocationId` and their rows are
8148
+ * stamped. The enrolled gallery is skipped (D197).
8149
+ * - `seal` — ROWS ONLY, no bytes. Every row whose `locationId` is NULL is
8150
+ * stamped with `toLocationId`. `toLocationId` here is the id the
8151
+ * bytes ALREADY sit on — today's `eventMedia` default — because
8152
+ * a NULL row means "wherever `eventMedia` points *now*", and the
8153
+ * instant a repoint moves that pointer the row reads from the
8154
+ * new disk while its bytes are on the old one.
8155
+ * - `gallery` — the inverse selection of `move`: ONLY the retention-exempt
8156
+ * (enrolled-gallery) rows, which `move` deliberately skips.
8157
+ * `galleryMedia` is `cardinality: 'single'`, so this pass can
8158
+ * never run beside a live second location: it is stop-the-world
8159
+ * by construction, which is acceptable only because the gallery
8160
+ * is a few KB per enrolled sample.
8161
+ */
8162
+ var MediaRelocateModeSchema = _enum([
8163
+ "move",
8164
+ "seal",
8165
+ "gallery"
8166
+ ]);
8141
8167
  var RelocateMediaInputSchema = object({
8142
8168
  toLocationId: string(),
8143
- throttleMbps: number().min(1).max(1e3).optional()
8169
+ throttleMbps: number().min(1).max(1e3).optional(),
8170
+ /** Omitted = `move`, the pre-existing behaviour. */
8171
+ mode: MediaRelocateModeSchema.optional()
8172
+ });
8173
+ /** How many rows still carry NO `locationId` — the population a repoint would
8174
+ * silently re-aim at a disk that does not hold their bytes. Zero is the only
8175
+ * value that permits a non-blocking `eventMedia` cutover. */
8176
+ var UnstampedEventMediaCountSchema = object({
8177
+ media: number().int().nonnegative(),
8178
+ retrainFrames: number().int().nonnegative(),
8179
+ total: number().int().nonnegative()
8144
8180
  });
8145
8181
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
8146
- /** The independently selectable logical storage classes. `recordings`
8147
- * encompasses the high and mid segment profiles; `recordingsLow` is low
8148
- * segments; `eventMedia` is post-analysis blobs. */
8182
+ /** The independently selectable logical storage classes — every class
8183
+ * `storage.listLocationDeclarations` reports, so an operator never meets a
8184
+ * Zod enum error where they should meet an explanation.
8185
+ *
8186
+ * `recordings` encompasses the high and mid segment profiles; `recordingsLow`
8187
+ * is low segments; `eventMedia` is post-analysis blobs; `galleryMedia` is the
8188
+ * enrolled gallery; `backups` is the system backup archive. The last two have
8189
+ * their own rules — see {@link StorageMigrationFindingCodeSchema}. */
8149
8190
  var StorageMigrationClassSchema = _enum([
8150
8191
  "recordings",
8151
8192
  "recordingsLow",
8152
- "eventMedia"
8193
+ "eventMedia",
8194
+ "backups",
8195
+ "galleryMedia"
8153
8196
  ]);
8154
8197
  /** A destination is always an existing, fully-qualified location id. The
8155
8198
  * migration API intentionally never changes a source location's `basePath`:
@@ -8157,20 +8200,56 @@ var StorageMigrationClassSchema = _enum([
8157
8200
  var StorageMigrationDestinationsSchema = object({
8158
8201
  recordings: string().min(1).optional(),
8159
8202
  recordingsLow: string().min(1).optional(),
8160
- eventMedia: string().min(1).optional()
8203
+ eventMedia: string().min(1).optional(),
8204
+ backups: string().min(1).optional(),
8205
+ galleryMedia: string().min(1).optional()
8161
8206
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8207
+ /**
8208
+ * How a migration sequences the cutover against the byte move.
8209
+ *
8210
+ * - `blocking` — the historical order: pause, move every byte, repoint,
8211
+ * resume. Recording is stopped for the whole move. Right
8212
+ * for a small or a cold class, and the only legal mode for
8213
+ * a `cardinality: 'single'` class.
8214
+ * - `nonBlocking` — repoint FIRST, drain behind: seal, pause, repoint,
8215
+ * refresh, resume, then move the past with everything
8216
+ * running. The pause is three bounded instants (a detach +
8217
+ * attach round, a write-gate drain, a lease) instead of one
8218
+ * bounded by bytes. 1.09 TB at 7–14 MB/s is thirty hours of
8219
+ * stopped recording under `blocking`; the same move is
8220
+ * seconds of stopped recording under `nonBlocking`.
8221
+ *
8222
+ * The mode is on the JOB, not only on the input, because `status` is where an
8223
+ * operator finds out which one is running.
8224
+ */
8225
+ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8162
8226
  /** Shared input for planning and starting an orchestrated storage migration. */
8163
8227
  var StorageMigrationInputSchema = object({
8164
8228
  destinations: StorageMigrationDestinationsSchema,
8165
- throttleMbps: number().min(1).max(1e3).optional()
8229
+ throttleMbps: number().min(1).max(1e3).optional(),
8230
+ /** Omitted = `blocking`, which stays the default. */
8231
+ mode: StorageMigrationModeSchema.optional()
8166
8232
  });
8167
- /** The durable coordinator state machine. The only phase that changes default
8168
- * locations is `repointing`, after every selected mover has completed and been
8169
- * verified. */
8233
+ /**
8234
+ * The durable coordinator state machine.
8235
+ *
8236
+ * `blocking`:
8237
+ * planning → pausing → moving → verifying → repointing → refreshing → resuming → done
8238
+ *
8239
+ * `nonBlocking`:
8240
+ * planning → sealing → pausing → repointing → refreshing → resuming → draining → verifying → done
8241
+ *
8242
+ * Same phases, different order plus two new ones — not a second mover.
8243
+ * `sealing` closes the `eventMedia` NULL-row hole BEFORE anything is paused;
8244
+ * `draining` runs the same movers UNLEASED, after every writer is back up.
8245
+ * `repointing` is still the only phase that changes a default location.
8246
+ */
8170
8247
  var StorageMigrationPhaseSchema = _enum([
8171
8248
  "planning",
8249
+ "sealing",
8172
8250
  "pausing",
8173
8251
  "moving",
8252
+ "draining",
8174
8253
  "verifying",
8175
8254
  "repointing",
8176
8255
  "refreshing",
@@ -8195,6 +8274,9 @@ var StorageMigrationMoveSchema = object({
8195
8274
  var StorageMigrationJobSchema = object({
8196
8275
  jobId: string(),
8197
8276
  phase: StorageMigrationPhaseSchema,
8277
+ /** Which order this job is running. `status` is the only place an operator
8278
+ * can tell a seconds-long cutover from a thirty-hour one. */
8279
+ mode: StorageMigrationModeSchema,
8198
8280
  destinations: StorageMigrationDestinationsSchema,
8199
8281
  throttleMbps: number(),
8200
8282
  moves: array(StorageMigrationMoveSchema),
@@ -8207,13 +8289,30 @@ var StorageMigrationJobSchema = object({
8207
8289
  finishedAt: number().nullable(),
8208
8290
  error: string().nullable()
8209
8291
  });
8292
+ var StorageMigrationFindingSchema = object({
8293
+ code: _enum([
8294
+ "sharesDeviceWithSource",
8295
+ "deviceIdentityUnknown",
8296
+ "unstampedEventMediaRows",
8297
+ "blockingOnly",
8298
+ "noMover"
8299
+ ]),
8300
+ storageClass: StorageMigrationClassSchema,
8301
+ /** Human-readable, already carrying the ids and counts. */
8302
+ message: string()
8303
+ });
8210
8304
  var StorageMigrationPlanSchema = object({
8211
8305
  destinations: StorageMigrationDestinationsSchema,
8306
+ /** The mode this plan was built for. A plan is only valid for its mode: the
8307
+ * `eventMedia` seal gate and the single-cardinality refusal both depend on
8308
+ * it. */
8309
+ mode: StorageMigrationModeSchema,
8212
8310
  moves: array(object({
8213
8311
  storageClass: StorageMigrationClassSchema,
8214
8312
  fromLocationId: string(),
8215
8313
  toLocationId: string()
8216
- }))
8314
+ })),
8315
+ findings: array(StorageMigrationFindingSchema)
8217
8316
  });
8218
8317
  /**
8219
8318
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -19011,7 +19110,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19011
19110
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
19012
19111
  kind: "mutation",
19013
19112
  auth: "admin"
19014
- }), method(object({}), array(RelocateJobSchema).readonly(), {
19113
+ }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(object({}), array(RelocateJobSchema).readonly(), {
19015
19114
  kind: "query",
19016
19115
  auth: "admin"
19017
19116
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
@@ -21022,7 +21121,10 @@ method(object({
21022
21121
  }), StorageLocationSchema, {
21023
21122
  kind: "mutation",
21024
21123
  auth: "admin"
21025
- }), method(object({ id: string() }), _void(), {
21124
+ }), method(object({
21125
+ id: string(),
21126
+ force: boolean().optional()
21127
+ }), _void(), {
21026
21128
  kind: "mutation",
21027
21129
  auth: "admin"
21028
21130
  }), method(object({ id: string() }), object({
@@ -25134,6 +25236,33 @@ var intercomCapability = {
25134
25236
  deviceNative: true,
25135
25237
  mode: "singleton",
25136
25238
  deviceTypes: [DeviceType.Camera],
25239
+ /**
25240
+ * **Auth tier: `protected` on every method — deliberate, and load-bearing.**
25241
+ *
25242
+ * Talking through a camera is an OPERATE action, not a CONFIGURE one. This
25243
+ * cap has no configuration surface at all: all six methods open, feed and
25244
+ * close one live audio session against one `deviceId`. That is the same
25245
+ * authority as `ptz.move` or `snapshot.getSnapshot`, both `protected` — and
25246
+ * the opposite of `ptz.savePreset` / `snapshot.invalidateCache`, which are
25247
+ * `admin` because they change what the device IS.
25248
+ *
25249
+ * `protected` does not mean ungated: `protectedProcedure` runs the
25250
+ * `METHOD_ACCESS_MAP` scope check, and every method here is `scope: 'device'`
25251
+ * with `access: 'create'` and a `deviceId` in its input. So a caller needs a
25252
+ * grant that covers THAT camera at `create` — a `camera-viewer` (`view`
25253
+ * only) still cannot talk, and a grant on camera 5 cannot talk through
25254
+ * camera 7.
25255
+ *
25256
+ * Every method was `auth: 'admin'` from the initial commit, which made the
25257
+ * cap unreachable by every non-admin principal — `adminProcedure` throws
25258
+ * `FORBIDDEN: Admin required` BEFORE the scope check runs, so the scope
25259
+ * machinery generated for this cap (`METHOD_ACCESS_MAP`,
25260
+ * `DEVICE_SCOPED_CAPS`, `METHOD_DEVICE_SELECTORS`) was complete and dead. The
25261
+ * `camera-operator` scope preset has promised "PTZ control, intercom,
25262
+ * snapshots" since that same commit; the promise could not be kept. Recorded
25263
+ * as D289; `scripts/check-scope-preset-promises.ts` now fails the build if a
25264
+ * preset promises a cap no row of that preset can reach.
25265
+ */
25137
25266
  methods: {
25138
25267
  /**
25139
25268
  * Open a server-side WebRTC audio-only session. Returns an SDP
@@ -25146,7 +25275,7 @@ var intercomCapability = {
25146
25275
  sdpOffer: string()
25147
25276
  }), {
25148
25277
  kind: "mutation",
25149
- auth: "admin"
25278
+ auth: "protected"
25150
25279
  }),
25151
25280
  handleAnswer: method(object({
25152
25281
  deviceId: number(),
@@ -25154,7 +25283,7 @@ var intercomCapability = {
25154
25283
  sdpAnswer: string()
25155
25284
  }), _void(), {
25156
25285
  kind: "mutation",
25157
- auth: "admin"
25286
+ auth: "protected"
25158
25287
  }),
25159
25288
  /** Close explicitly. Server also auto-closes on 30s idle. */
25160
25289
  stopSession: method(object({
@@ -25162,7 +25291,7 @@ var intercomCapability = {
25162
25291
  sessionId: string()
25163
25292
  }), _void(), {
25164
25293
  kind: "mutation",
25165
- auth: "admin"
25294
+ auth: "protected"
25166
25295
  }),
25167
25296
  /**
25168
25297
  * Open a raw-PCM talk session (no WebRTC SDP plumbing). Used by
@@ -25175,7 +25304,7 @@ var intercomCapability = {
25175
25304
  */
25176
25305
  startTalkSession: method(object({ deviceId: number() }), object({ sessionId: string() }), {
25177
25306
  kind: "mutation",
25178
- auth: "admin"
25307
+ auth: "protected"
25179
25308
  }),
25180
25309
  /**
25181
25310
  * Push one chunk of talk-back audio onto the active talk session.
@@ -25210,12 +25339,12 @@ var intercomCapability = {
25210
25339
  sequenceNumber: number().int()
25211
25340
  }), object({ accepted: boolean() }), {
25212
25341
  kind: "mutation",
25213
- auth: "admin"
25342
+ auth: "protected"
25214
25343
  }),
25215
25344
  /** Close the raw-PCM talk session. Idempotent. */
25216
25345
  endTalkSession: method(object({ deviceId: number() }), _void(), {
25217
25346
  kind: "mutation",
25218
- auth: "admin"
25347
+ auth: "protected"
25219
25348
  })
25220
25349
  },
25221
25350
  events: { onStatusChanged: { data: object({
@@ -34894,6 +35023,12 @@ Object.freeze({
34894
35023
  addonId: null,
34895
35024
  access: "create"
34896
35025
  },
35026
+ "pipelineAnalytics.countUnstampedEventMedia": {
35027
+ capName: "pipeline-analytics",
35028
+ capScope: "device",
35029
+ addonId: null,
35030
+ access: "view"
35031
+ },
34897
35032
  "pipelineAnalytics.deleteDeviceEvents": {
34898
35033
  capName: "pipeline-analytics",
34899
35034
  capScope: "device",
package/dist/addon.mjs CHANGED
@@ -8115,18 +8115,61 @@ var RelocateFootageInputSchema = object({
8115
8115
  * `RecordingConfig.enabled` or camera wrapper bindings. */
8116
8116
  var StorageMigrationLeaseInputSchema = object({ leaseId: string().min(1) });
8117
8117
  var StorageMigrationFootageMoveInputSchema = RelocateFootageInputSchema.extend({ leaseId: string().min(1) });
8118
+ /**
8119
+ * What a `relocateMedia` pass DOES. One engine, three passes — never a second
8120
+ * mover (the engine already walks both collections with a timestamp cursor and
8121
+ * already has a stamp-without-copy path).
8122
+ *
8123
+ * - `move` — the default and the historical behaviour: event-media and
8124
+ * retrain blobs move to `toLocationId` and their rows are
8125
+ * stamped. The enrolled gallery is skipped (D197).
8126
+ * - `seal` — ROWS ONLY, no bytes. Every row whose `locationId` is NULL is
8127
+ * stamped with `toLocationId`. `toLocationId` here is the id the
8128
+ * bytes ALREADY sit on — today's `eventMedia` default — because
8129
+ * a NULL row means "wherever `eventMedia` points *now*", and the
8130
+ * instant a repoint moves that pointer the row reads from the
8131
+ * new disk while its bytes are on the old one.
8132
+ * - `gallery` — the inverse selection of `move`: ONLY the retention-exempt
8133
+ * (enrolled-gallery) rows, which `move` deliberately skips.
8134
+ * `galleryMedia` is `cardinality: 'single'`, so this pass can
8135
+ * never run beside a live second location: it is stop-the-world
8136
+ * by construction, which is acceptable only because the gallery
8137
+ * is a few KB per enrolled sample.
8138
+ */
8139
+ var MediaRelocateModeSchema = _enum([
8140
+ "move",
8141
+ "seal",
8142
+ "gallery"
8143
+ ]);
8118
8144
  var RelocateMediaInputSchema = object({
8119
8145
  toLocationId: string(),
8120
- throttleMbps: number().min(1).max(1e3).optional()
8146
+ throttleMbps: number().min(1).max(1e3).optional(),
8147
+ /** Omitted = `move`, the pre-existing behaviour. */
8148
+ mode: MediaRelocateModeSchema.optional()
8149
+ });
8150
+ /** How many rows still carry NO `locationId` — the population a repoint would
8151
+ * silently re-aim at a disk that does not hold their bytes. Zero is the only
8152
+ * value that permits a non-blocking `eventMedia` cutover. */
8153
+ var UnstampedEventMediaCountSchema = object({
8154
+ media: number().int().nonnegative(),
8155
+ retrainFrames: number().int().nonnegative(),
8156
+ total: number().int().nonnegative()
8121
8157
  });
8122
8158
  var StorageMigrationMediaMoveInputSchema = RelocateMediaInputSchema.extend({ leaseId: string().min(1) });
8123
- /** The independently selectable logical storage classes. `recordings`
8124
- * encompasses the high and mid segment profiles; `recordingsLow` is low
8125
- * segments; `eventMedia` is post-analysis blobs. */
8159
+ /** The independently selectable logical storage classes — every class
8160
+ * `storage.listLocationDeclarations` reports, so an operator never meets a
8161
+ * Zod enum error where they should meet an explanation.
8162
+ *
8163
+ * `recordings` encompasses the high and mid segment profiles; `recordingsLow`
8164
+ * is low segments; `eventMedia` is post-analysis blobs; `galleryMedia` is the
8165
+ * enrolled gallery; `backups` is the system backup archive. The last two have
8166
+ * their own rules — see {@link StorageMigrationFindingCodeSchema}. */
8126
8167
  var StorageMigrationClassSchema = _enum([
8127
8168
  "recordings",
8128
8169
  "recordingsLow",
8129
- "eventMedia"
8170
+ "eventMedia",
8171
+ "backups",
8172
+ "galleryMedia"
8130
8173
  ]);
8131
8174
  /** A destination is always an existing, fully-qualified location id. The
8132
8175
  * migration API intentionally never changes a source location's `basePath`:
@@ -8134,20 +8177,56 @@ var StorageMigrationClassSchema = _enum([
8134
8177
  var StorageMigrationDestinationsSchema = object({
8135
8178
  recordings: string().min(1).optional(),
8136
8179
  recordingsLow: string().min(1).optional(),
8137
- eventMedia: string().min(1).optional()
8180
+ eventMedia: string().min(1).optional(),
8181
+ backups: string().min(1).optional(),
8182
+ galleryMedia: string().min(1).optional()
8138
8183
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8184
+ /**
8185
+ * How a migration sequences the cutover against the byte move.
8186
+ *
8187
+ * - `blocking` — the historical order: pause, move every byte, repoint,
8188
+ * resume. Recording is stopped for the whole move. Right
8189
+ * for a small or a cold class, and the only legal mode for
8190
+ * a `cardinality: 'single'` class.
8191
+ * - `nonBlocking` — repoint FIRST, drain behind: seal, pause, repoint,
8192
+ * refresh, resume, then move the past with everything
8193
+ * running. The pause is three bounded instants (a detach +
8194
+ * attach round, a write-gate drain, a lease) instead of one
8195
+ * bounded by bytes. 1.09 TB at 7–14 MB/s is thirty hours of
8196
+ * stopped recording under `blocking`; the same move is
8197
+ * seconds of stopped recording under `nonBlocking`.
8198
+ *
8199
+ * The mode is on the JOB, not only on the input, because `status` is where an
8200
+ * operator finds out which one is running.
8201
+ */
8202
+ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8139
8203
  /** Shared input for planning and starting an orchestrated storage migration. */
8140
8204
  var StorageMigrationInputSchema = object({
8141
8205
  destinations: StorageMigrationDestinationsSchema,
8142
- throttleMbps: number().min(1).max(1e3).optional()
8206
+ throttleMbps: number().min(1).max(1e3).optional(),
8207
+ /** Omitted = `blocking`, which stays the default. */
8208
+ mode: StorageMigrationModeSchema.optional()
8143
8209
  });
8144
- /** The durable coordinator state machine. The only phase that changes default
8145
- * locations is `repointing`, after every selected mover has completed and been
8146
- * verified. */
8210
+ /**
8211
+ * The durable coordinator state machine.
8212
+ *
8213
+ * `blocking`:
8214
+ * planning → pausing → moving → verifying → repointing → refreshing → resuming → done
8215
+ *
8216
+ * `nonBlocking`:
8217
+ * planning → sealing → pausing → repointing → refreshing → resuming → draining → verifying → done
8218
+ *
8219
+ * Same phases, different order plus two new ones — not a second mover.
8220
+ * `sealing` closes the `eventMedia` NULL-row hole BEFORE anything is paused;
8221
+ * `draining` runs the same movers UNLEASED, after every writer is back up.
8222
+ * `repointing` is still the only phase that changes a default location.
8223
+ */
8147
8224
  var StorageMigrationPhaseSchema = _enum([
8148
8225
  "planning",
8226
+ "sealing",
8149
8227
  "pausing",
8150
8228
  "moving",
8229
+ "draining",
8151
8230
  "verifying",
8152
8231
  "repointing",
8153
8232
  "refreshing",
@@ -8172,6 +8251,9 @@ var StorageMigrationMoveSchema = object({
8172
8251
  var StorageMigrationJobSchema = object({
8173
8252
  jobId: string(),
8174
8253
  phase: StorageMigrationPhaseSchema,
8254
+ /** Which order this job is running. `status` is the only place an operator
8255
+ * can tell a seconds-long cutover from a thirty-hour one. */
8256
+ mode: StorageMigrationModeSchema,
8175
8257
  destinations: StorageMigrationDestinationsSchema,
8176
8258
  throttleMbps: number(),
8177
8259
  moves: array(StorageMigrationMoveSchema),
@@ -8184,13 +8266,30 @@ var StorageMigrationJobSchema = object({
8184
8266
  finishedAt: number().nullable(),
8185
8267
  error: string().nullable()
8186
8268
  });
8269
+ var StorageMigrationFindingSchema = object({
8270
+ code: _enum([
8271
+ "sharesDeviceWithSource",
8272
+ "deviceIdentityUnknown",
8273
+ "unstampedEventMediaRows",
8274
+ "blockingOnly",
8275
+ "noMover"
8276
+ ]),
8277
+ storageClass: StorageMigrationClassSchema,
8278
+ /** Human-readable, already carrying the ids and counts. */
8279
+ message: string()
8280
+ });
8187
8281
  var StorageMigrationPlanSchema = object({
8188
8282
  destinations: StorageMigrationDestinationsSchema,
8283
+ /** The mode this plan was built for. A plan is only valid for its mode: the
8284
+ * `eventMedia` seal gate and the single-cardinality refusal both depend on
8285
+ * it. */
8286
+ mode: StorageMigrationModeSchema,
8189
8287
  moves: array(object({
8190
8288
  storageClass: StorageMigrationClassSchema,
8191
8289
  fromLocationId: string(),
8192
8290
  toLocationId: string()
8193
- }))
8291
+ })),
8292
+ findings: array(StorageMigrationFindingSchema)
8194
8293
  });
8195
8294
  /**
8196
8295
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
@@ -18988,7 +19087,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18988
19087
  }), method(RelocateMediaInputSchema, object({ jobId: string() }), {
18989
19088
  kind: "mutation",
18990
19089
  auth: "admin"
18991
- }), method(object({}), array(RelocateJobSchema).readonly(), {
19090
+ }), method(object({}), UnstampedEventMediaCountSchema, { auth: "admin" }), method(object({}), array(RelocateJobSchema).readonly(), {
18992
19091
  kind: "query",
18993
19092
  auth: "admin"
18994
19093
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
@@ -20999,7 +21098,10 @@ method(object({
20999
21098
  }), StorageLocationSchema, {
21000
21099
  kind: "mutation",
21001
21100
  auth: "admin"
21002
- }), method(object({ id: string() }), _void(), {
21101
+ }), method(object({
21102
+ id: string(),
21103
+ force: boolean().optional()
21104
+ }), _void(), {
21003
21105
  kind: "mutation",
21004
21106
  auth: "admin"
21005
21107
  }), method(object({ id: string() }), object({
@@ -25111,6 +25213,33 @@ var intercomCapability = {
25111
25213
  deviceNative: true,
25112
25214
  mode: "singleton",
25113
25215
  deviceTypes: [DeviceType.Camera],
25216
+ /**
25217
+ * **Auth tier: `protected` on every method — deliberate, and load-bearing.**
25218
+ *
25219
+ * Talking through a camera is an OPERATE action, not a CONFIGURE one. This
25220
+ * cap has no configuration surface at all: all six methods open, feed and
25221
+ * close one live audio session against one `deviceId`. That is the same
25222
+ * authority as `ptz.move` or `snapshot.getSnapshot`, both `protected` — and
25223
+ * the opposite of `ptz.savePreset` / `snapshot.invalidateCache`, which are
25224
+ * `admin` because they change what the device IS.
25225
+ *
25226
+ * `protected` does not mean ungated: `protectedProcedure` runs the
25227
+ * `METHOD_ACCESS_MAP` scope check, and every method here is `scope: 'device'`
25228
+ * with `access: 'create'` and a `deviceId` in its input. So a caller needs a
25229
+ * grant that covers THAT camera at `create` — a `camera-viewer` (`view`
25230
+ * only) still cannot talk, and a grant on camera 5 cannot talk through
25231
+ * camera 7.
25232
+ *
25233
+ * Every method was `auth: 'admin'` from the initial commit, which made the
25234
+ * cap unreachable by every non-admin principal — `adminProcedure` throws
25235
+ * `FORBIDDEN: Admin required` BEFORE the scope check runs, so the scope
25236
+ * machinery generated for this cap (`METHOD_ACCESS_MAP`,
25237
+ * `DEVICE_SCOPED_CAPS`, `METHOD_DEVICE_SELECTORS`) was complete and dead. The
25238
+ * `camera-operator` scope preset has promised "PTZ control, intercom,
25239
+ * snapshots" since that same commit; the promise could not be kept. Recorded
25240
+ * as D289; `scripts/check-scope-preset-promises.ts` now fails the build if a
25241
+ * preset promises a cap no row of that preset can reach.
25242
+ */
25114
25243
  methods: {
25115
25244
  /**
25116
25245
  * Open a server-side WebRTC audio-only session. Returns an SDP
@@ -25123,7 +25252,7 @@ var intercomCapability = {
25123
25252
  sdpOffer: string()
25124
25253
  }), {
25125
25254
  kind: "mutation",
25126
- auth: "admin"
25255
+ auth: "protected"
25127
25256
  }),
25128
25257
  handleAnswer: method(object({
25129
25258
  deviceId: number(),
@@ -25131,7 +25260,7 @@ var intercomCapability = {
25131
25260
  sdpAnswer: string()
25132
25261
  }), _void(), {
25133
25262
  kind: "mutation",
25134
- auth: "admin"
25263
+ auth: "protected"
25135
25264
  }),
25136
25265
  /** Close explicitly. Server also auto-closes on 30s idle. */
25137
25266
  stopSession: method(object({
@@ -25139,7 +25268,7 @@ var intercomCapability = {
25139
25268
  sessionId: string()
25140
25269
  }), _void(), {
25141
25270
  kind: "mutation",
25142
- auth: "admin"
25271
+ auth: "protected"
25143
25272
  }),
25144
25273
  /**
25145
25274
  * Open a raw-PCM talk session (no WebRTC SDP plumbing). Used by
@@ -25152,7 +25281,7 @@ var intercomCapability = {
25152
25281
  */
25153
25282
  startTalkSession: method(object({ deviceId: number() }), object({ sessionId: string() }), {
25154
25283
  kind: "mutation",
25155
- auth: "admin"
25284
+ auth: "protected"
25156
25285
  }),
25157
25286
  /**
25158
25287
  * Push one chunk of talk-back audio onto the active talk session.
@@ -25187,12 +25316,12 @@ var intercomCapability = {
25187
25316
  sequenceNumber: number().int()
25188
25317
  }), object({ accepted: boolean() }), {
25189
25318
  kind: "mutation",
25190
- auth: "admin"
25319
+ auth: "protected"
25191
25320
  }),
25192
25321
  /** Close the raw-PCM talk session. Idempotent. */
25193
25322
  endTalkSession: method(object({ deviceId: number() }), _void(), {
25194
25323
  kind: "mutation",
25195
- auth: "admin"
25324
+ auth: "protected"
25196
25325
  })
25197
25326
  },
25198
25327
  events: { onStatusChanged: { data: object({
@@ -34871,6 +35000,12 @@ Object.freeze({
34871
35000
  addonId: null,
34872
35001
  access: "create"
34873
35002
  },
35003
+ "pipelineAnalytics.countUnstampedEventMedia": {
35004
+ capName: "pipeline-analytics",
35005
+ capScope: "device",
35006
+ addonId: null,
35007
+ access: "view"
35008
+ },
34874
35009
  "pipelineAnalytics.deleteDeviceEvents": {
34875
35010
  capName: "pipeline-analytics",
34876
35011
  capScope: "device",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",