@camstack/addon-mqtt-broker 1.2.95 → 1.2.96

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.
@@ -5390,6 +5390,86 @@ var ZodIssueCode = {
5390
5390
  /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5391
5391
  var ZodFirstPartyTypeKind;
5392
5392
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5393
+ //#endregion
5394
+ //#region ../types/dist/sleep-BnujYGPe.mjs
5395
+ /**
5396
+ * The audio chunk plane's byte format, and the ONE expansion from a coded
5397
+ * window to float samples (D455).
5398
+ *
5399
+ * ## Why a format at all
5400
+ *
5401
+ * D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
5402
+ * RATE, and the one consumer that needs 16 kHz resamples next to the model.
5403
+ * It left the FORMAT alone — the broker still turned each G.711 byte into a
5404
+ * 4-byte f32le sample before the bytes entered the transport, so every leg of
5405
+ * the plane carried four times the source. The plane crosses hub-main twice on
5406
+ * the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
5407
+ *
5408
+ * So the plane carries the source BYTES too, and whoever needs floats expands
5409
+ * them where it needs them. That is the same argument D450 made for the rate,
5410
+ * one step further along the same wire.
5411
+ *
5412
+ * ## Why the expansion lives here
5413
+ *
5414
+ * Two packages need it and they must never disagree: `addon-pipeline`'s broker
5415
+ * (which still has to serve a subscriber that did NOT ask for coded bytes —
5416
+ * `AudioChunkPlane` expands per subscription) and
5417
+ * `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
5418
+ * f32le window to the analyzer cap, whose `AudioChunkInput` contract is
5419
+ * unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
5420
+ * into their own dist (`self-contained` externals), so this travels with a
5421
+ * `camstack deploy` and needs no published server.
5422
+ *
5423
+ * A second μ-law table anywhere else is the defect this module exists to
5424
+ * prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
5425
+ * direction for the WebRTC egress — a different transform, not a copy.)
5426
+ *
5427
+ * ## Absent means f32le
5428
+ *
5429
+ * `format` is optional on the wire and its absence means `f32le` — today's
5430
+ * bytes, byte for byte. A peer that never heard of the field is served what it
5431
+ * has always been served, because the broker only emits a coded window to a
5432
+ * subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
5433
+ * That is the D448 `rawForward` negotiation, and it is what makes this
5434
+ * deployable one addon at a time across three nodes.
5435
+ */
5436
+ /** Every byte format the audio chunk plane can carry. `f32le` is the default. */
5437
+ var AUDIO_CHUNK_FORMATS = [
5438
+ "f32le",
5439
+ "pcmu",
5440
+ "pcma"
5441
+ ];
5442
+ /**
5443
+ * Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
5444
+ * to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
5445
+ *
5446
+ * Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
5447
+ * it buffers the coded bytes and the plane's consumers expand.
5448
+ */
5449
+ function buildUlawTable() {
5450
+ const table = new Float32Array(256);
5451
+ for (let i = 0; i < 256; i++) {
5452
+ const complemented = ~i & 255;
5453
+ const sign = (complemented & 128) !== 0 ? -1 : 1;
5454
+ const exponent = complemented >> 4 & 7;
5455
+ table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
5456
+ }
5457
+ return table;
5458
+ }
5459
+ /** Build the A-law decode table (ITU-T G.711). */
5460
+ function buildAlawTable() {
5461
+ const table = new Float32Array(256);
5462
+ for (let i = 0; i < 256; i++) {
5463
+ const xored = i ^ 85;
5464
+ const sign = (xored & 128) !== 0 ? 1 : -1;
5465
+ const exponent = xored >> 4 & 7;
5466
+ const mantissa = xored & 15;
5467
+ table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
5468
+ }
5469
+ return table;
5470
+ }
5471
+ buildUlawTable();
5472
+ buildAlawTable();
5393
5473
  Object.fromEntries([
5394
5474
  {
5395
5475
  id: "overview",
@@ -6682,11 +6762,20 @@ var SubscribeFramesResultSchema = object({
6682
6762
  * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
6683
6763
  * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
6684
6764
  */
6765
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
6685
6766
  var DecodedAudioChunkSchema = object({
6686
6767
  data: _instanceof(Uint8Array),
6687
6768
  sampleRate: number().int().positive(),
6688
6769
  channels: number().int().positive(),
6689
- timestamp: number()
6770
+ timestamp: number(),
6771
+ /**
6772
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
6773
+ * byte, for any peer that never heard of this field. A coded window
6774
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
6775
+ * subscription that DECLARED it accepts one, so absence can never mean
6776
+ * "coded bytes a consumer will read as floats" (D455).
6777
+ */
6778
+ format: AudioChunkFormatSchema.optional()
6690
6779
  });
6691
6780
  /**
6692
6781
  * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
@@ -6698,7 +6787,18 @@ var DecodedAudioChunkSchema = object({
6698
6787
  var SubscribeAudioChunksInputSchema = object({
6699
6788
  brokerId: string(),
6700
6789
  /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
6701
- tag: string().optional()
6790
+ tag: string().optional(),
6791
+ /**
6792
+ * Byte formats this subscriber can READ, best first. The broker serves the
6793
+ * chunk's own format when it is in this list and expands to `f32le`
6794
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
6795
+ *
6796
+ * Absent (or without the source format) means `f32le` — the behaviour every
6797
+ * subscriber had before D455, unchanged. This is the negotiation half of
6798
+ * the source-bytes lever: it is what lets the broker and its consumers
6799
+ * deploy one at a time across three nodes.
6800
+ */
6801
+ accept: array(AudioChunkFormatSchema).readonly().optional()
6702
6802
  });
6703
6803
  /** Result of `stream-broker.subscribeAudioChunks`. */
6704
6804
  var SubscribeAudioChunksResultSchema = object({
@@ -10670,6 +10770,51 @@ var AudioAnalysisSettingsSchema = object({
10670
10770
  minConfidence: number().min(0).max(1).default(.3),
10671
10771
  allowedClasses: array(string()).default([])
10672
10772
  });
10773
+ /**
10774
+ * `attachDevice` — the analyzer PULLS a camera's audio from the broker (D461).
10775
+ *
10776
+ * Until D461 the orchestrator drained the broker's chunk plane, accumulated
10777
+ * ~1 s windows and pushed them back out as `analyseChunk`. It neither produced
10778
+ * nor consumed the audio: the PCM crossed hub-main twice for a process that
10779
+ * only buffered it. `attachDevice` inverts the direction — the analyzer opens
10780
+ * its own `subscribeAudioChunks` against the broker and the subscriber IS the
10781
+ * decoder, so the coded G.711 bytes D455 put on the plane stay coded all the
10782
+ * way to the one expansion that feeds the model.
10783
+ *
10784
+ * The orchestrator still owns the POLICY (the `audioMode` gate, the on-motion
10785
+ * window, the per-device node assignment, the settings read) and therefore
10786
+ * still owns the attach/detach pair. It no longer owns the bytes.
10787
+ */
10788
+ var AudioAttachDeviceInputSchema = object({
10789
+ deviceId: number(),
10790
+ /** Broker id (`<deviceId>/<camStreamId>`) carrying this camera's audio. */
10791
+ brokerId: string(),
10792
+ /**
10793
+ * `clusterRoles.ingestNode` — the node whose broker owns the source dial.
10794
+ * Every `streamBroker` call the attachment makes is pinned to it, exactly as
10795
+ * the orchestrator's poller pinned them before the move.
10796
+ */
10797
+ ingestNodeId: string(),
10798
+ /**
10799
+ * Resolved once by the orchestrator at attach time, exactly as it was read
10800
+ * once per subscription before D461. The analyzer does NOT re-resolve per
10801
+ * window: a settings change re-attaches, which is what always happened.
10802
+ */
10803
+ settings: AudioAnalysisSettingsSchema
10804
+ });
10805
+ var AudioAttachDeviceResultSchema = object({
10806
+ /** False only when the analyzer is shutting down and refused to attach. */
10807
+ attached: boolean(),
10808
+ /**
10809
+ * True when the attachment replaced a live one for the same device. An
10810
+ * attach is idempotent by REPLACEMENT — two pollers on one camera would
10811
+ * double the broker's fanout and neither would know about the other.
10812
+ */
10813
+ replaced: boolean()
10814
+ });
10815
+ var AudioDetachDeviceResultSchema = object({
10816
+ /** False when no attachment existed — detach is idempotent. */
10817
+ detached: boolean() });
10673
10818
  var AudioClassificationResultSchema = object({
10674
10819
  labels: array(AudioClassificationLabelSchema).readonly(),
10675
10820
  rawLabels: array(AudioClassificationLabelSchema).readonly().optional(),
@@ -10678,7 +10823,7 @@ var AudioClassificationResultSchema = object({
10678
10823
  method(object({
10679
10824
  chunk: AudioChunkInputSchema,
10680
10825
  settings: AudioAnalysisSettingsSchema
10681
- }), AudioAnalysisResultSchema.nullable(), { kind: "mutation" }), method(AudioChunkInputSchema, AudioClassificationResultSchema, { timeoutMs: 3e4 }), method(_void(), boolean()), method(_void(), _void(), { kind: "mutation" }), method(_void(), object({ backend: string() }), {
10826
+ }), AudioAnalysisResultSchema.nullable(), { kind: "mutation" }), method(AudioChunkInputSchema, AudioClassificationResultSchema, { timeoutMs: 3e4 }), method(AudioAttachDeviceInputSchema, AudioAttachDeviceResultSchema, { kind: "mutation" }), method(object({ deviceId: number() }), AudioDetachDeviceResultSchema, { kind: "mutation" }), method(_void(), boolean()), method(_void(), _void(), { kind: "mutation" }), method(_void(), object({ backend: string() }), {
10682
10827
  kind: "mutation",
10683
10828
  auth: "admin"
10684
10829
  });
@@ -30641,12 +30786,24 @@ Object.freeze({
30641
30786
  addonId: null,
30642
30787
  access: "create"
30643
30788
  },
30789
+ "audioAnalyzer.attachDevice": {
30790
+ capName: "audio-analyzer",
30791
+ capScope: "system",
30792
+ addonId: null,
30793
+ access: "create"
30794
+ },
30644
30795
  "audioAnalyzer.classify": {
30645
30796
  capName: "audio-analyzer",
30646
30797
  capScope: "system",
30647
30798
  addonId: null,
30648
30799
  access: "view"
30649
30800
  },
30801
+ "audioAnalyzer.detachDevice": {
30802
+ capName: "audio-analyzer",
30803
+ capScope: "system",
30804
+ addonId: null,
30805
+ access: "create"
30806
+ },
30650
30807
  "audioAnalyzer.dispose": {
30651
30808
  capName: "audio-analyzer",
30652
30809
  capScope: "system",
@@ -36490,11 +36647,21 @@ Object.freeze({
36490
36647
  form: "single",
36491
36648
  optional: false
36492
36649
  }],
36650
+ "audioAnalyzer.attachDevice": [{
36651
+ name: "deviceId",
36652
+ form: "single",
36653
+ optional: false
36654
+ }],
36493
36655
  "audioAnalyzer.classify": [{
36494
36656
  name: "deviceId",
36495
36657
  form: "single",
36496
36658
  optional: true
36497
36659
  }],
36660
+ "audioAnalyzer.detachDevice": [{
36661
+ name: "deviceId",
36662
+ form: "single",
36663
+ optional: false
36664
+ }],
36498
36665
  "audioMetrics.getCurrentSnapshot": [{
36499
36666
  name: "deviceId",
36500
36667
  form: "single",
@@ -38346,6 +38513,52 @@ Object.freeze({
38346
38513
  "network-access": "ingress",
38347
38514
  "smtp-provider": "email"
38348
38515
  });
38516
+ var G711_SCALE_CORRECTION_DB = {
38517
+ PCMU: 20 * Math.log10(4),
38518
+ PCMA: 20 * Math.log10(8)
38519
+ };
38520
+ /**
38521
+ * Restate a dBFS number that was MEASURED through the pre-epoch decoder as the
38522
+ * same intent on the ITU-T scale (D460).
38523
+ *
38524
+ * ## When this applies, and when it is the wrong thing to reach for
38525
+ *
38526
+ * An absolute-dBFS number in this repo is one of two things, and only one of
38527
+ * them converts:
38528
+ *
38529
+ * - **A statement about the scale** — "-55 dBFS is near silence", "-25 dBFS
38530
+ * is loud". It was true on the ITU-T scale before the epoch and it is true
38531
+ * after. The defect was never in the number; it was that 19 of this hub's
38532
+ * 25 cameras did not obey it. Converting such a number takes something
38533
+ * correct and makes it wrong, in order to preserve a bug.
38534
+ * - **A measurement taken through the old decoder** — a value someone read
38535
+ * off a meter that under-reported by exactly 4× (PCMU) or 8× (PCMA). It
38536
+ * describes a sound that was really {@link G711_SCALE_CORRECTION_DB} dB
38537
+ * louder. That is what this function is for.
38538
+ *
38539
+ * Telling the two apart is a question about PROVENANCE, not about arithmetic,
38540
+ * and it cannot be answered from the number. It is answered by the comment the
38541
+ * author left — which is why `scripts/check-dbfs-era.mts` makes leaving one
38542
+ * mandatory.
38543
+ *
38544
+ * ## Why a function and not a typed-in number
38545
+ *
38546
+ * `-55 + 12.04` written into a source file is, six months later, completely
38547
+ * indistinguishable from a threshold somebody simply preferred. Calling this
38548
+ * keeps the derivation, the law, and the original measurement all visible at
38549
+ * the call site, so a future reader can disagree with the *premise* instead of
38550
+ * having to reverse-engineer the sum.
38551
+ *
38552
+ * **This is not a runtime gain.** It converts an authored CONSTANT once, where
38553
+ * it is declared. It must never be applied to a live sample or a stored
38554
+ * `AudioEvent.dbfs`: the decoder is correct now, and a second authority
38555
+ * adjusting numbers the decoder already got right is the original defect with
38556
+ * an extra place to argue with (D459).
38557
+ */
38558
+ function ituDbfsFromPreEpoch(law, authoredDbfs) {
38559
+ return authoredDbfs + G711_SCALE_CORRECTION_DB[law];
38560
+ }
38561
+ Math.round(ituDbfsFromPreEpoch("PCMU", -55));
38349
38562
  /** Schema defaults — an untouched sub-field must author exactly these. */
38350
38563
  var NC_AUDIO_DEFAULTS = {
38351
38564
  hitPercent: 60,
@@ -5385,6 +5385,86 @@ var ZodIssueCode = {
5385
5385
  /** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
5386
5386
  var ZodFirstPartyTypeKind;
5387
5387
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5388
+ //#endregion
5389
+ //#region ../types/dist/sleep-BnujYGPe.mjs
5390
+ /**
5391
+ * The audio chunk plane's byte format, and the ONE expansion from a coded
5392
+ * window to float samples (D455).
5393
+ *
5394
+ * ## Why a format at all
5395
+ *
5396
+ * D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
5397
+ * RATE, and the one consumer that needs 16 kHz resamples next to the model.
5398
+ * It left the FORMAT alone — the broker still turned each G.711 byte into a
5399
+ * 4-byte f32le sample before the bytes entered the transport, so every leg of
5400
+ * the plane carried four times the source. The plane crosses hub-main twice on
5401
+ * the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
5402
+ *
5403
+ * So the plane carries the source BYTES too, and whoever needs floats expands
5404
+ * them where it needs them. That is the same argument D450 made for the rate,
5405
+ * one step further along the same wire.
5406
+ *
5407
+ * ## Why the expansion lives here
5408
+ *
5409
+ * Two packages need it and they must never disagree: `addon-pipeline`'s broker
5410
+ * (which still has to serve a subscriber that did NOT ask for coded bytes —
5411
+ * `AudioChunkPlane` expands per subscription) and
5412
+ * `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
5413
+ * f32le window to the analyzer cap, whose `AudioChunkInput` contract is
5414
+ * unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
5415
+ * into their own dist (`self-contained` externals), so this travels with a
5416
+ * `camstack deploy` and needs no published server.
5417
+ *
5418
+ * A second μ-law table anywhere else is the defect this module exists to
5419
+ * prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
5420
+ * direction for the WebRTC egress — a different transform, not a copy.)
5421
+ *
5422
+ * ## Absent means f32le
5423
+ *
5424
+ * `format` is optional on the wire and its absence means `f32le` — today's
5425
+ * bytes, byte for byte. A peer that never heard of the field is served what it
5426
+ * has always been served, because the broker only emits a coded window to a
5427
+ * subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
5428
+ * That is the D448 `rawForward` negotiation, and it is what makes this
5429
+ * deployable one addon at a time across three nodes.
5430
+ */
5431
+ /** Every byte format the audio chunk plane can carry. `f32le` is the default. */
5432
+ var AUDIO_CHUNK_FORMATS = [
5433
+ "f32le",
5434
+ "pcmu",
5435
+ "pcma"
5436
+ ];
5437
+ /**
5438
+ * Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
5439
+ * to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
5440
+ *
5441
+ * Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
5442
+ * it buffers the coded bytes and the plane's consumers expand.
5443
+ */
5444
+ function buildUlawTable() {
5445
+ const table = new Float32Array(256);
5446
+ for (let i = 0; i < 256; i++) {
5447
+ const complemented = ~i & 255;
5448
+ const sign = (complemented & 128) !== 0 ? -1 : 1;
5449
+ const exponent = complemented >> 4 & 7;
5450
+ table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
5451
+ }
5452
+ return table;
5453
+ }
5454
+ /** Build the A-law decode table (ITU-T G.711). */
5455
+ function buildAlawTable() {
5456
+ const table = new Float32Array(256);
5457
+ for (let i = 0; i < 256; i++) {
5458
+ const xored = i ^ 85;
5459
+ const sign = (xored & 128) !== 0 ? 1 : -1;
5460
+ const exponent = xored >> 4 & 7;
5461
+ const mantissa = xored & 15;
5462
+ table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
5463
+ }
5464
+ return table;
5465
+ }
5466
+ buildUlawTable();
5467
+ buildAlawTable();
5388
5468
  Object.fromEntries([
5389
5469
  {
5390
5470
  id: "overview",
@@ -6677,11 +6757,20 @@ var SubscribeFramesResultSchema = object({
6677
6757
  * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
6678
6758
  * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
6679
6759
  */
6760
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
6680
6761
  var DecodedAudioChunkSchema = object({
6681
6762
  data: _instanceof(Uint8Array),
6682
6763
  sampleRate: number().int().positive(),
6683
6764
  channels: number().int().positive(),
6684
- timestamp: number()
6765
+ timestamp: number(),
6766
+ /**
6767
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
6768
+ * byte, for any peer that never heard of this field. A coded window
6769
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
6770
+ * subscription that DECLARED it accepts one, so absence can never mean
6771
+ * "coded bytes a consumer will read as floats" (D455).
6772
+ */
6773
+ format: AudioChunkFormatSchema.optional()
6685
6774
  });
6686
6775
  /**
6687
6776
  * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
@@ -6693,7 +6782,18 @@ var DecodedAudioChunkSchema = object({
6693
6782
  var SubscribeAudioChunksInputSchema = object({
6694
6783
  brokerId: string(),
6695
6784
  /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
6696
- tag: string().optional()
6785
+ tag: string().optional(),
6786
+ /**
6787
+ * Byte formats this subscriber can READ, best first. The broker serves the
6788
+ * chunk's own format when it is in this list and expands to `f32le`
6789
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
6790
+ *
6791
+ * Absent (or without the source format) means `f32le` — the behaviour every
6792
+ * subscriber had before D455, unchanged. This is the negotiation half of
6793
+ * the source-bytes lever: it is what lets the broker and its consumers
6794
+ * deploy one at a time across three nodes.
6795
+ */
6796
+ accept: array(AudioChunkFormatSchema).readonly().optional()
6697
6797
  });
6698
6798
  /** Result of `stream-broker.subscribeAudioChunks`. */
6699
6799
  var SubscribeAudioChunksResultSchema = object({
@@ -10665,6 +10765,51 @@ var AudioAnalysisSettingsSchema = object({
10665
10765
  minConfidence: number().min(0).max(1).default(.3),
10666
10766
  allowedClasses: array(string()).default([])
10667
10767
  });
10768
+ /**
10769
+ * `attachDevice` — the analyzer PULLS a camera's audio from the broker (D461).
10770
+ *
10771
+ * Until D461 the orchestrator drained the broker's chunk plane, accumulated
10772
+ * ~1 s windows and pushed them back out as `analyseChunk`. It neither produced
10773
+ * nor consumed the audio: the PCM crossed hub-main twice for a process that
10774
+ * only buffered it. `attachDevice` inverts the direction — the analyzer opens
10775
+ * its own `subscribeAudioChunks` against the broker and the subscriber IS the
10776
+ * decoder, so the coded G.711 bytes D455 put on the plane stay coded all the
10777
+ * way to the one expansion that feeds the model.
10778
+ *
10779
+ * The orchestrator still owns the POLICY (the `audioMode` gate, the on-motion
10780
+ * window, the per-device node assignment, the settings read) and therefore
10781
+ * still owns the attach/detach pair. It no longer owns the bytes.
10782
+ */
10783
+ var AudioAttachDeviceInputSchema = object({
10784
+ deviceId: number(),
10785
+ /** Broker id (`<deviceId>/<camStreamId>`) carrying this camera's audio. */
10786
+ brokerId: string(),
10787
+ /**
10788
+ * `clusterRoles.ingestNode` — the node whose broker owns the source dial.
10789
+ * Every `streamBroker` call the attachment makes is pinned to it, exactly as
10790
+ * the orchestrator's poller pinned them before the move.
10791
+ */
10792
+ ingestNodeId: string(),
10793
+ /**
10794
+ * Resolved once by the orchestrator at attach time, exactly as it was read
10795
+ * once per subscription before D461. The analyzer does NOT re-resolve per
10796
+ * window: a settings change re-attaches, which is what always happened.
10797
+ */
10798
+ settings: AudioAnalysisSettingsSchema
10799
+ });
10800
+ var AudioAttachDeviceResultSchema = object({
10801
+ /** False only when the analyzer is shutting down and refused to attach. */
10802
+ attached: boolean(),
10803
+ /**
10804
+ * True when the attachment replaced a live one for the same device. An
10805
+ * attach is idempotent by REPLACEMENT — two pollers on one camera would
10806
+ * double the broker's fanout and neither would know about the other.
10807
+ */
10808
+ replaced: boolean()
10809
+ });
10810
+ var AudioDetachDeviceResultSchema = object({
10811
+ /** False when no attachment existed — detach is idempotent. */
10812
+ detached: boolean() });
10668
10813
  var AudioClassificationResultSchema = object({
10669
10814
  labels: array(AudioClassificationLabelSchema).readonly(),
10670
10815
  rawLabels: array(AudioClassificationLabelSchema).readonly().optional(),
@@ -10673,7 +10818,7 @@ var AudioClassificationResultSchema = object({
10673
10818
  method(object({
10674
10819
  chunk: AudioChunkInputSchema,
10675
10820
  settings: AudioAnalysisSettingsSchema
10676
- }), AudioAnalysisResultSchema.nullable(), { kind: "mutation" }), method(AudioChunkInputSchema, AudioClassificationResultSchema, { timeoutMs: 3e4 }), method(_void(), boolean()), method(_void(), _void(), { kind: "mutation" }), method(_void(), object({ backend: string() }), {
10821
+ }), AudioAnalysisResultSchema.nullable(), { kind: "mutation" }), method(AudioChunkInputSchema, AudioClassificationResultSchema, { timeoutMs: 3e4 }), method(AudioAttachDeviceInputSchema, AudioAttachDeviceResultSchema, { kind: "mutation" }), method(object({ deviceId: number() }), AudioDetachDeviceResultSchema, { kind: "mutation" }), method(_void(), boolean()), method(_void(), _void(), { kind: "mutation" }), method(_void(), object({ backend: string() }), {
10677
10822
  kind: "mutation",
10678
10823
  auth: "admin"
10679
10824
  });
@@ -30636,12 +30781,24 @@ Object.freeze({
30636
30781
  addonId: null,
30637
30782
  access: "create"
30638
30783
  },
30784
+ "audioAnalyzer.attachDevice": {
30785
+ capName: "audio-analyzer",
30786
+ capScope: "system",
30787
+ addonId: null,
30788
+ access: "create"
30789
+ },
30639
30790
  "audioAnalyzer.classify": {
30640
30791
  capName: "audio-analyzer",
30641
30792
  capScope: "system",
30642
30793
  addonId: null,
30643
30794
  access: "view"
30644
30795
  },
30796
+ "audioAnalyzer.detachDevice": {
30797
+ capName: "audio-analyzer",
30798
+ capScope: "system",
30799
+ addonId: null,
30800
+ access: "create"
30801
+ },
30645
30802
  "audioAnalyzer.dispose": {
30646
30803
  capName: "audio-analyzer",
30647
30804
  capScope: "system",
@@ -36485,11 +36642,21 @@ Object.freeze({
36485
36642
  form: "single",
36486
36643
  optional: false
36487
36644
  }],
36645
+ "audioAnalyzer.attachDevice": [{
36646
+ name: "deviceId",
36647
+ form: "single",
36648
+ optional: false
36649
+ }],
36488
36650
  "audioAnalyzer.classify": [{
36489
36651
  name: "deviceId",
36490
36652
  form: "single",
36491
36653
  optional: true
36492
36654
  }],
36655
+ "audioAnalyzer.detachDevice": [{
36656
+ name: "deviceId",
36657
+ form: "single",
36658
+ optional: false
36659
+ }],
36493
36660
  "audioMetrics.getCurrentSnapshot": [{
36494
36661
  name: "deviceId",
36495
36662
  form: "single",
@@ -38341,6 +38508,52 @@ Object.freeze({
38341
38508
  "network-access": "ingress",
38342
38509
  "smtp-provider": "email"
38343
38510
  });
38511
+ var G711_SCALE_CORRECTION_DB = {
38512
+ PCMU: 20 * Math.log10(4),
38513
+ PCMA: 20 * Math.log10(8)
38514
+ };
38515
+ /**
38516
+ * Restate a dBFS number that was MEASURED through the pre-epoch decoder as the
38517
+ * same intent on the ITU-T scale (D460).
38518
+ *
38519
+ * ## When this applies, and when it is the wrong thing to reach for
38520
+ *
38521
+ * An absolute-dBFS number in this repo is one of two things, and only one of
38522
+ * them converts:
38523
+ *
38524
+ * - **A statement about the scale** — "-55 dBFS is near silence", "-25 dBFS
38525
+ * is loud". It was true on the ITU-T scale before the epoch and it is true
38526
+ * after. The defect was never in the number; it was that 19 of this hub's
38527
+ * 25 cameras did not obey it. Converting such a number takes something
38528
+ * correct and makes it wrong, in order to preserve a bug.
38529
+ * - **A measurement taken through the old decoder** — a value someone read
38530
+ * off a meter that under-reported by exactly 4× (PCMU) or 8× (PCMA). It
38531
+ * describes a sound that was really {@link G711_SCALE_CORRECTION_DB} dB
38532
+ * louder. That is what this function is for.
38533
+ *
38534
+ * Telling the two apart is a question about PROVENANCE, not about arithmetic,
38535
+ * and it cannot be answered from the number. It is answered by the comment the
38536
+ * author left — which is why `scripts/check-dbfs-era.mts` makes leaving one
38537
+ * mandatory.
38538
+ *
38539
+ * ## Why a function and not a typed-in number
38540
+ *
38541
+ * `-55 + 12.04` written into a source file is, six months later, completely
38542
+ * indistinguishable from a threshold somebody simply preferred. Calling this
38543
+ * keeps the derivation, the law, and the original measurement all visible at
38544
+ * the call site, so a future reader can disagree with the *premise* instead of
38545
+ * having to reverse-engineer the sum.
38546
+ *
38547
+ * **This is not a runtime gain.** It converts an authored CONSTANT once, where
38548
+ * it is declared. It must never be applied to a live sample or a stored
38549
+ * `AudioEvent.dbfs`: the decoder is correct now, and a second authority
38550
+ * adjusting numbers the decoder already got right is the original defect with
38551
+ * an extra place to argue with (D459).
38552
+ */
38553
+ function ituDbfsFromPreEpoch(law, authoredDbfs) {
38554
+ return authoredDbfs + G711_SCALE_CORRECTION_DB[law];
38555
+ }
38556
+ Math.round(ituDbfsFromPreEpoch("PCMU", -55));
38344
38557
  /** Schema defaults — an untouched sub-field must author exactly these. */
38345
38558
  var NC_AUDIO_DEFAULTS = {
38346
38559
  hitPercent: 60,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-mqtt-broker",
3
- "version": "1.2.95",
3
+ "version": "1.2.96",
4
4
  "description": "MQTT broker registry addon for CamStack — manages external broker entries + an optional embedded aedes broker. Consumers spin up their own `mqtt.js` clients via the `mqtt-broker` cap.",
5
5
  "keywords": [
6
6
  "camstack",