@camstack/addon-pipeline 1.2.198 → 1.2.200

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 (43) hide show
  1. package/dist/audio-analyzer/index.js +2 -2
  2. package/dist/audio-analyzer/index.mjs +2 -2
  3. package/dist/{default-detection-model-B5v8qEI0.js → default-detection-model-BsmxtyZx.js} +1 -1
  4. package/dist/{default-detection-model-CASpAESz.mjs → default-detection-model-c1LY6thP.mjs} +1 -1
  5. package/dist/detection-pipeline/index.js +5 -5
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-BxP0SvPz.js → dist-D2YTTkRf.js} +436 -419
  8. package/dist/{dist-oFIHnD88.mjs → dist-mEsrAAc3.mjs} +436 -419
  9. package/dist/{lazy-sharp-VKOG3WW4.js → lazy-sharp-B5bW34uC.js} +1 -1
  10. package/dist/motion-wasm/index.js +2 -2
  11. package/dist/motion-wasm/index.mjs +1 -1
  12. package/dist/{node-B4zETsJ9.mjs → node-CUxep5tc.mjs} +1 -1
  13. package/dist/{node-BIKrNEtS.js → node-x3YokJkD.js} +1 -1
  14. package/dist/pipeline-runner/index.js +8 -7
  15. package/dist/pipeline-runner/index.mjs +7 -6
  16. package/dist/{process-memory-D9xYwm6L.mjs → process-memory-BCqpTruN.mjs} +1 -1
  17. package/dist/{process-memory-CMHOmj75.js → process-memory-CFc4D9A2.js} +1 -1
  18. package/dist/recorder/index.js +169 -334
  19. package/dist/recorder/index.mjs +164 -329
  20. package/dist/restream-intent-B1Difcnq.js +313 -0
  21. package/dist/restream-intent-DWe2kInY.mjs +248 -0
  22. package/dist/{prebuffer-DmBYRQWg.mjs → retire-root-keys-Csb8z2f3.js} +12 -64
  23. package/dist/{prebuffer-DqdeLSsk.js → retire-root-keys-DZvlso2l.mjs} +1 -99
  24. package/dist/{segment-demux-js-DLwL1vmq.js → segment-demux-js-DUZVURQd.js} +1 -1
  25. package/dist/{segment-demux-js-DZysrgdC.mjs → segment-demux-js-xHtB450F.mjs} +1 -1
  26. package/dist/session-decode/decode-worker-child.js +25 -5
  27. package/dist/session-decode/decode-worker-child.mjs +24 -4
  28. package/dist/stream-broker/_stub.js +2 -2
  29. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-C2eTdxvd.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CLXImQ3R.mjs} +3 -3
  30. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-NqqfpkYF.mjs +26 -0
  31. package/dist/stream-broker/{_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dj_qoc1e.mjs → _virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D62O5bRa.mjs} +1 -1
  32. package/dist/stream-broker/demux-worker-child.js +1 -1
  33. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  34. package/dist/stream-broker/{hostInit-C8tu7NS4.mjs → hostInit-D_cZQhBA.mjs} +3 -3
  35. package/dist/stream-broker/index.js +181 -48
  36. package/dist/stream-broker/index.mjs +172 -39
  37. package/dist/stream-broker/remoteEntry.js +1 -1
  38. package/dist/{worker-protocol-D2mMjUXv.js → worker-protocol-CRDaBlJl.js} +1 -1
  39. package/dist/{worker-protocol-DFlZm_wr.mjs → worker-protocol-oUamwZSn.mjs} +1 -1
  40. package/package.json +1 -1
  41. package/dist/restream-intent-B4BXZra7.mjs +0 -72
  42. package/dist/restream-intent-Cv9x3jmu.js +0 -89
  43. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BcjLrxhF.mjs +0 -26
@@ -1,9 +1,9 @@
1
- import { Bt as hydrateSchema, Ct as storageEvictableCapability, D as RecordingSignalStatusSchema, E as RecordingConfigSchema, Ft as BaseAddon, H as deriveBatteryPresence, Jt as sleep$1, Lt as DeviceFeature, Ot as errMsg, P as batteryCapability, R as customAction, Rt as DeviceType, T as RECORDING_EXPORT_MAX_READ_BYTES, Tt as stripRetiredBandPreBufferSec, V as defineCustomActions, W as deriveRecordingMode, Wt as nodePin, Xt as array, Y as evaluateSensorEdge, Yt as _enum, Zt as boolean, _t as recordingSignalCapability, an as string, at as migrateLegacyDeviceSignalConfig, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, et as isFirstLevelMacroClass, g as ExportRecordSchema, gt as recordingExportCapability, ht as recordingCapability, i as BatteryStatusSchema, in as record, nn as object, qt as selectAssignedProfileSlots, rn as preprocess, sn as EventCategory, tn as number, tt as loadContributionCapability, x as OpsLogEntrySchema, xt as resolveRecordingProfiles } from "../dist-oFIHnD88.mjs";
2
- import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-B4zETsJ9.mjs";
1
+ import { Bt as hydrateSchema, Ct as storageEvictableCapability, D as RecordingSignalStatusSchema, E as RecordingConfigSchema, Ft as BaseAddon, H as deriveBatteryPresence, Jt as sleep$1, Lt as DeviceFeature, Ot as errMsg, P as batteryCapability, R as customAction, Rt as DeviceType, T as RECORDING_EXPORT_MAX_READ_BYTES, Tt as stripRetiredBandPreBufferSec, V as defineCustomActions, W as deriveRecordingMode, Wt as nodePin, Xt as array, Y as evaluateSensorEdge, Yt as _enum, Zt as boolean, _t as recordingSignalCapability, an as string, at as migrateLegacyDeviceSignalConfig, d as DEFAULT_EVENTS_BAND_BUFFER_SEC, et as isFirstLevelMacroClass, g as ExportRecordSchema, gt as recordingExportCapability, ht as recordingCapability, i as BatteryStatusSchema, in as record, nn as object, qt as selectAssignedProfileSlots, rn as preprocess, sn as EventCategory, tn as number, tt as loadContributionCapability, x as OpsLogEntrySchema, xt as resolveRecordingProfiles } from "../dist-mEsrAAc3.mjs";
2
+ import { i as NO_COST_CLAIM, t as ChildCostRegistry } from "../node-CUxep5tc.mjs";
3
3
  import { t as resolveHubHostname } from "../hub-hostname-cCknRYKj.mjs";
4
- import { r as withRecordingIntent } from "../restream-intent-B4BXZra7.mjs";
4
+ import { c as PREBUFFER_MAX_MS, r as RECORDING_PRE_ROLL_REQUEST_MS, s as withRecordingIntent } from "../restream-intent-DWe2kInY.mjs";
5
5
  import { n as startEventLoopStallMonitor } from "../event-loop-stall-monitor-DXLNBMkY.mjs";
6
- import { a as retireRootKeys, o as RowMapStore, t as PREBUFFER_MAX_MS } from "../prebuffer-DmBYRQWg.mjs";
6
+ import { n as RowMapStore, t as retireRootKeys } from "../retire-root-keys-DZvlso2l.mjs";
7
7
  import { randomUUID } from "node:crypto";
8
8
  import { createReadStream, promises, realpathSync } from "node:fs";
9
9
  import path from "node:path";
@@ -7727,96 +7727,59 @@ function runFfmpeg(renderDeps, args, ext) {
7727
7727
  });
7728
7728
  });
7729
7729
  }
7730
- //#endregion
7731
- //#region src/recorder/addon/roots.ts
7732
- /**
7733
- * Storage-root resolution + boot hydrate for the recorder addon.
7734
- *
7735
- * Recordings live in the managed `recordings` / `recordingsLow` storage
7736
- * locations (operator-configurable, multi-disk-ready), resolved through the
7737
- * `storage` cap. The v2 `RecordingIndex` is hydrated by walking each location
7738
- * root's `<deviceId>/` subtree on disk — segment paths encode start/dur/bytes,
7739
- * so the in-memory index can never drift from disk. (We walk the filesystem
7740
- * directly instead of `storage.list` because that cap lists only a directory's
7741
- * IMMEDIATE children, not the `<deviceId>/<profile>/Y/M/D/H/*.m4s` tree — using
7742
- * it dropped the entire index to ~empty on every restart/rescan; the recorder
7743
- * already does local fs I/O for stat/move on these same roots.)
7744
- */
7745
- /** The two recordings location types the recorder addon writes/reads. */
7746
- var PLACEMENT_LOCATION_TYPES = ["recordings", "recordingsLow"];
7747
- var RECORDING_LOCATION_TYPES = new Set(PLACEMENT_LOCATION_TYPES);
7730
+ var RECORDING_LOCATION_TYPES = new Set(["recordings", "recordingsLow"]);
7748
7731
  /**
7749
7732
  * The recordings CLASS a profile belongs to. The one place this rule is
7750
- * written: `resolvePlacement`, the planner's demand type and the repoint check
7751
- * must agree on it, and three copies of `profile === 'low' ? … : …` is how they
7752
- * stop agreeing.
7733
+ * written: `resolvePlacement`, the planner's demand type and the placement
7734
+ * census must agree on it, and three copies of `profile === 'low' ? … : …` is
7735
+ * how they stop agreeing.
7753
7736
  */
7754
7737
  function placementTypeForProfile(profile) {
7755
7738
  return profile === "low" ? "recordingsLow" : "recordings";
7756
7739
  }
7757
- /** The profile that routes to each class — the probe `authorityLocations` asks
7758
- * `resolvePlacement` with, so the answer is derived from the same function the
7759
- * unassigned path uses rather than from a second reading of `isDefault`. */
7760
- var CLASS_PROBE_PROFILE = {
7761
- recordings: "high",
7762
- recordingsLow: "low"
7763
- };
7764
- /**
7765
- * Where each recordings class writes when NOTHING has planned it — the location
7766
- * `resolvePlacement` picks, which is the type default unless the default is
7767
- * gone, read-only, or absent from the pool.
7768
- *
7769
- * This is the AUTHORITY a stored assignment is measured against (D298). A
7770
- * repoint (`storage.setDefaultLocations`, an operator making another disk the
7771
- * default, a default deleted) changes exactly this map, and a stored assignment
7772
- * that survives that change is a plan that outlived the pool it was planned
7773
- * for.
7774
- *
7775
- * A class with nothing writable is ABSENT from the map, not guessed at: `place`
7776
- * already throws loudly on that path, and an authority nobody can compute must
7777
- * invalidate nothing.
7778
- */
7779
- function authorityLocations(pool) {
7780
- const out = /* @__PURE__ */ new Map();
7781
- for (const type of PLACEMENT_LOCATION_TYPES) try {
7782
- out.set(type, resolvePlacement(pool, CLASS_PROBE_PROFILE[type]).id);
7783
- } catch {}
7784
- return out;
7785
- }
7786
7740
  /**
7787
7741
  * Narrow a resolved location list to the ones placement may write to: the
7788
- * operator-enabled ones. A location the operator has not opted in
7789
- * (`enabled === false`) is still READ, age-swept and drained — it is simply
7742
+ * enabled ones — ALL of them, together. A location the operator has not opted
7743
+ * in (`enabled === false`) is still READ, age-swept and drained — it is simply
7790
7744
  * never a write target, exactly like a read-only one.
7791
7745
  *
7792
- * When no enabled location is writable the type DEFAULTS serve instead, loudly:
7793
- * the default of a type is guaranteed enabled by the orchestrator, so this can
7794
- * only happen through a hand-edited store — and a recorder that writes nowhere
7795
- * is worse than one that writes to the default and complains.
7746
+ * When no enabled location is writable EVERY location serves instead, loudly.
7747
+ * The orchestrator refuses to leave a recordings class without an enabled
7748
+ * location, so this is reachable only through a hand-edited store — and a
7749
+ * recorder that writes nowhere is worse than one that writes under protest.
7796
7750
  */
7797
7751
  function placementPool(locations) {
7798
7752
  const enabled = locations.filter((l) => l.enabled !== false);
7799
7753
  if (enabled.some((l) => !l.readOnly)) return {
7800
7754
  locations: enabled,
7801
- fellBackToDefault: false
7802
- };
7803
- const defaults = locations.filter((l) => l.isDefault === true);
7804
- if (defaults.some((l) => !l.readOnly)) return {
7805
- locations: defaults,
7806
- fellBackToDefault: true
7755
+ fellBackToDisabled: false
7807
7756
  };
7808
7757
  return {
7809
7758
  locations,
7810
- fellBackToDefault: true
7759
+ fellBackToDisabled: true
7811
7760
  };
7812
7761
  }
7813
7762
  /**
7814
- * Route a recording profile to its storage location by the location's declared
7815
- * `type`: the `low` profile prefers the `recordingsLow` location, every other
7816
- * profile the `recordings` location. READ-ONLY locations are never write
7817
- * targets (the drain model): when the preferred type has only read-only
7818
- * instances, any writable recordings-type location serves. Throws when NO
7819
- * writable location exists — recording cannot start, and that must be loud.
7763
+ * Route a recording profile to a storage location, for a (camera, profile) the
7764
+ * PLANNER has not placed yet — the first attach of a new camera, before the
7765
+ * 30 s pass runs.
7766
+ *
7767
+ * The rule, in order:
7768
+ * 1. the location's declared `type` — `low` prefers `recordingsLow`, every
7769
+ * other profile prefers `recordings`; when the preferred type has only
7770
+ * read-only instances, any writable recordings-type location serves;
7771
+ * 2. among those, the one with the most MEASURED headroom
7772
+ * (`min(free − minFree floor, maxUsedGb − used)`) — the same signal the
7773
+ * planner balances on, so the bootstrap pick and the plan agree instead of
7774
+ * the plan having to undo the pick at the next tick;
7775
+ * 3. ties, and a pool where nothing was measured, fall to the
7776
+ * lexicographically first id — deterministic, never "whichever came back
7777
+ * first from the cap".
7778
+ *
7779
+ * There is no preferred instance any more (D383): every enabled location of a
7780
+ * class is a write target, and which one a camera gets is a capacity question,
7781
+ * not a flag. Throws when NO writable location exists — recording cannot start,
7782
+ * and that must be loud.
7820
7783
  */
7821
7784
  function resolvePlacement(locations, profile) {
7822
7785
  if (locations.length === 0) throw new Error("recorder controller: no recordings storage locations resolved");
@@ -7824,7 +7787,15 @@ function resolvePlacement(locations, profile) {
7824
7787
  if (writable.length === 0) throw new Error("recorder controller: every recordings location is read-only — nowhere to write");
7825
7788
  const preferType = profile === "low" ? "recordingsLow" : "recordings";
7826
7789
  const preferred = writable.filter((l) => l.type === preferType);
7827
- return preferred.find((l) => l.isDefault) ?? preferred[0] ?? writable.find((l) => l.isDefault) ?? writable[0];
7790
+ return emptiestOf(preferred.length > 0 ? preferred : writable);
7791
+ }
7792
+ /** The candidate with the most measured headroom; ties (and an unmeasured
7793
+ * pool, where every headroom is absent) by lexicographic id. */
7794
+ function emptiestOf(candidates) {
7795
+ return candidates.toSorted((a, b) => {
7796
+ const byHeadroom = (b.headroomBytes ?? 0) - (a.headroomBytes ?? 0);
7797
+ return byHeadroom !== 0 ? byHeadroom : a.id.localeCompare(b.id);
7798
+ })[0];
7828
7799
  }
7829
7800
  /**
7830
7801
  * Attribute a discovered segment on an aliased physical root by its profile,
@@ -7874,7 +7845,6 @@ async function resolveRecordingsLocations(api, logger) {
7874
7845
  relativePath: ""
7875
7846
  }),
7876
7847
  readOnly: l.config["readOnly"] === true,
7877
- isDefault: l.isDefault,
7878
7848
  ...l.enabled === false ? { enabled: false } : {},
7879
7849
  ...numericConfig(l.config, "minFreePercent", "minFreePercent"),
7880
7850
  ...numericConfig(l.config, "maxUsedGb", "maxUsedGb")
@@ -10432,47 +10402,17 @@ Object.freeze({
10432
10402
  },
10433
10403
  "setDeviceConfig": { "config": { "__nested__": { "bands": [] } } }
10434
10404
  },
10435
- "storage": { "upsertLocation": {
10436
- "isDefault": false,
10437
- "isSystem": false
10438
- } },
10405
+ "storage": { "upsertLocation": { "isSystem": false } },
10439
10406
  "storage-provider": {
10440
- "beginDownload": { "location": { "__nested__": {
10441
- "isDefault": false,
10442
- "isSystem": false
10443
- } } },
10444
- "beginUpload": { "location": { "__nested__": {
10445
- "isDefault": false,
10446
- "isSystem": false
10447
- } } },
10448
- "delete": { "location": { "__nested__": {
10449
- "isDefault": false,
10450
- "isSystem": false
10451
- } } },
10452
- "exists": { "location": { "__nested__": {
10453
- "isDefault": false,
10454
- "isSystem": false
10455
- } } },
10456
- "getAvailableSpace": { "location": { "__nested__": {
10457
- "isDefault": false,
10458
- "isSystem": false
10459
- } } },
10460
- "list": { "location": { "__nested__": {
10461
- "isDefault": false,
10462
- "isSystem": false
10463
- } } },
10464
- "read": { "location": { "__nested__": {
10465
- "isDefault": false,
10466
- "isSystem": false
10467
- } } },
10468
- "resolve": { "location": { "__nested__": {
10469
- "isDefault": false,
10470
- "isSystem": false
10471
- } } },
10472
- "write": { "location": { "__nested__": {
10473
- "isDefault": false,
10474
- "isSystem": false
10475
- } } }
10407
+ "beginDownload": { "location": { "__nested__": { "isSystem": false } } },
10408
+ "beginUpload": { "location": { "__nested__": { "isSystem": false } } },
10409
+ "delete": { "location": { "__nested__": { "isSystem": false } } },
10410
+ "exists": { "location": { "__nested__": { "isSystem": false } } },
10411
+ "getAvailableSpace": { "location": { "__nested__": { "isSystem": false } } },
10412
+ "list": { "location": { "__nested__": { "isSystem": false } } },
10413
+ "read": { "location": { "__nested__": { "isSystem": false } } },
10414
+ "resolve": { "location": { "__nested__": { "isSystem": false } } },
10415
+ "write": { "location": { "__nested__": { "isSystem": false } } }
10476
10416
  },
10477
10417
  "stream-broker": {
10478
10418
  "produceEventMedia": {
@@ -11490,19 +11430,16 @@ var RecordingPlacementBlobSchema = object({
11490
11430
  /** locationId → the volume id last verified at that location's root. */
11491
11431
  volumeIds: record(string(), string()).default({}),
11492
11432
  /**
11493
- * Recordings class (`recordings` / `recordingsLow`) → the location
11494
- * `resolvePlacement` chose for it when these assignments were last planned.
11495
- *
11496
- * ABSENT means "nobody has recorded an authority yet", and that is never read
11497
- * as a repoint: shipping this field must not invalidate assignments that were
11498
- * made under an authority nothing wrote down. The first pass stamps it and
11499
- * invalidates nothing; every later change of it is a repoint.
11433
+ * DEAD KEY (D383) — the class authority a pass planned against, back when a
11434
+ * class had a default distinct from its pool. Declared so an existing blob
11435
+ * round-trips unchanged and so its removal is a decision on the record rather
11436
+ * than a silent strip. Never read, never written.
11500
11437
  */
11501
11438
  defaultLocations: record(string(), string()).default({}),
11502
11439
  /** When the blob was last written (operator-facing debugging only). */
11503
11440
  updatedAt: number().optional()
11504
11441
  });
11505
- /** Cold-start value: nothing assigned, nothing claimed, no authority recorded. */
11442
+ /** Cold-start value: nothing assigned, nothing claimed. */
11506
11443
  var EMPTY_PLACEMENT = {
11507
11444
  assignments: {},
11508
11445
  pins: {},
@@ -11551,31 +11488,27 @@ async function replaceAssignments(state, base, assignments) {
11551
11488
  });
11552
11489
  }
11553
11490
  /**
11554
- * The assignment record a pass leaves behind: the stored one, with the repoint
11555
- * applied, then the plan merged over it. **Nothing is ever removed** — a pass
11556
- * that plans a subset cannot delete a key it did not mention.
11491
+ * The assignment record a pass leaves behind: the stored one with the plan
11492
+ * merged over it. **Nothing is ever removed** — a pass that plans a subset
11493
+ * cannot delete a key it did not mention.
11557
11494
  *
11558
- * PURE, and shared by the write and the divergence report — a report computed
11495
+ * PURE, and shared by the write and the placement census — a report computed
11559
11496
  * from a second reading of the same rule is a report that can describe a record
11560
11497
  * the write did not produce.
11561
11498
  */
11562
11499
  function applyPlacementPass(base, pass) {
11563
11500
  const next = new Map(Object.entries(base));
11564
- for (const [key, locationId] of pass.repointed) next.set(key, locationId);
11565
11501
  for (const [key, locationId] of pass.planned) next.set(key, locationId);
11566
11502
  return next;
11567
11503
  }
11568
- /** Persist one work-scoped pass: apply the repoint, merge its plan, and stamp
11569
- * the authority the plan was resolved against. One write. */
11504
+ /** Persist one work-scoped pass: merge its plan over the stored record. One
11505
+ * write. */
11570
11506
  async function writePlacementPass(state, base, pass) {
11571
11507
  const assignments = {};
11572
11508
  for (const [key, locationId] of applyPlacementPass(base.assignments, pass)) assignments[key] = locationId;
11573
- const defaultLocations = {};
11574
- for (const [type, locationId] of pass.defaultLocations) defaultLocations[type] = locationId;
11575
11509
  await state.set({
11576
11510
  ...base,
11577
11511
  assignments,
11578
- defaultLocations,
11579
11512
  updatedAt: Date.now()
11580
11513
  });
11581
11514
  }
@@ -11824,14 +11757,6 @@ var NO_PLAN = {
11824
11757
  assignments: /* @__PURE__ */ new Map(),
11825
11758
  changes: []
11826
11759
  };
11827
- /** Is the authority already stamped, exactly? A pass that would change the
11828
- * stamp always writes, even when it moved nothing: an unstamped authority is
11829
- * an undetectable repoint. */
11830
- function sameStamp(stamped, authority) {
11831
- if (stamped.size !== authority.size) return false;
11832
- for (const [type, locationId] of authority) if (stamped.get(type) !== locationId) return false;
11833
- return true;
11834
- }
11835
11760
  /** Operator pins win over the planner for high/mid of the pinned camera. */
11836
11761
  function overlayPins(planned, pins) {
11837
11762
  const next = new Map(planned);
@@ -11859,14 +11784,11 @@ var PlacementService = class {
11859
11784
  /** The fair tie-break the PURE planner defers to (D37). Advisory state, and
11860
11785
  * consulted only for a tie the capacity signal cannot separate. */
11861
11786
  rotation = new PlacementRotation();
11862
- /** The divergent set last announced, as a stable signature. `null` ⇒ nothing
11863
- * announced yet, which is why an all-aligned fleet says nothing at boot. */
11864
- divergenceSignature = null;
11865
- /** When the standing divergence was last announced. */
11866
- divergenceReportedAt = null;
11867
- /** Repoint episodes already announced (`type:from->to`). Cleared the moment a
11868
- * pass sees no repoint at all — the next one is a NEW episode. */
11869
- repointAnnounced = /* @__PURE__ */ new Set();
11787
+ /** The census last announced, as a stable signature. `null` ⇒ nothing
11788
+ * announced yet. */
11789
+ censusSignature = null;
11790
+ /** When the census was last announced. */
11791
+ censusReportedAt = null;
11870
11792
  constructor(deps) {
11871
11793
  this.deps = deps;
11872
11794
  }
@@ -11901,15 +11823,14 @@ var PlacementService = class {
11901
11823
  async place(deviceId, profile) {
11902
11824
  const pool = this.pool();
11903
11825
  this.reprobeAfterBackoff(pool);
11904
- const authority = authorityLocations(pool);
11905
- const resolved = await this.assignedLocation(deviceId, profile, pool, authority) ?? resolvePlacement(pool, profile);
11826
+ const resolved = await this.assignedLocation(deviceId, profile, pool) ?? resolvePlacement(await this.withHeadroom(pool), profile);
11906
11827
  const usable = await this.ensureUsable(resolved, pool, profile, deviceId);
11907
11828
  if (this.everyLocationUnusable(pool)) {
11908
11829
  this.reportEveryLocationUnusable(deviceId, profile, resolved, pool);
11909
11830
  return resolved;
11910
11831
  }
11911
11832
  this.allUnusableReported.delete(deviceId);
11912
- await this.recordDecision(deviceId, profile, usable, resolved.id === usable.id, authority.get(placementTypeForProfile(profile)) ?? null);
11833
+ await this.recordDecision(deviceId, profile, usable, resolved.id === usable.id);
11913
11834
  return usable;
11914
11835
  }
11915
11836
  /**
@@ -11993,35 +11914,30 @@ var PlacementService = class {
11993
11914
  * to it, so an unknown flag is the only thing that counts as opted in.
11994
11915
  */
11995
11916
  pool() {
11996
- const { locations, fellBackToDefault } = placementPool(this.deps.locations());
11997
- if (fellBackToDefault) this.deps.logger.warn("recorder placement: NO recordings location is enabled for writing — falling back to the type default. Enable a location in Storage, or recordings will keep landing on the default.", { meta: { locationIds: locations.map((l) => l.id) } });
11917
+ const { locations, fellBackToDisabled } = placementPool(this.deps.locations());
11918
+ if (fellBackToDisabled) this.deps.logger.warn("recorder placement: NO recordings location is enabled for writing — writing to a DISABLED location under protest. Enable one in Storage.", { meta: { locationIds: locations.map((l) => l.id) } });
11998
11919
  return locations;
11999
11920
  }
12000
11921
  /**
12001
11922
  * The stored assignment for one (camera, profile), when it is still valid.
12002
11923
  *
12003
- * Three things invalidate it, and the third is the one this used to miss:
12004
- *
12005
- * 1. the location is absent from the current pool (deleted, opted out);
12006
- * 2. the location is read-only (an operator drain);
12007
- * 3. **the class was REPOINTED under it** — the authority stamped in the
12008
- * blob is not the authority now, and the assignment names neither.
11924
+ * Two things invalidate it, and under the operator's model (D383) there is
11925
+ * nothing else that can:
12009
11926
  *
12010
- * (1) and (2) both mean "this location is not a write target any more", so
12011
- * they only ever caught a repoint that took the old location out of service.
12012
- * A repoint of a class the operator is MIGRATING does the opposite: the
12013
- * source must stay enabled and writable, because it holds the history. So the
12014
- * assignment survived every attach, indefinitely, and `resolvePlacement`
12015
- * returning the new default changed nothing — it is the fallback, not the
12016
- * authority. On 2026-08-29 that left two of thirteen cameras writing to the
12017
- * disk the fleet was migrating off, silently (D298).
11927
+ * 1. the location is absent from the current POOL — deleted, or the
11928
+ * operator disabled it. The pool is the single write authority, so an
11929
+ * assignment naming a location outside it is not a divergence to report,
11930
+ * it is unrepresentable;
11931
+ * 2. the location is read-only (an operator drain).
12018
11932
  *
12019
- * The invalidation is a TRANSITION, not a policy: it fires only while the
12020
- * stamped authority disagrees with the live one, which the next placement pass
12021
- * ends by stamping. After that the plan is the authority again and a
12022
- * deliberate spread is honoured — and reported (`reportDivergence`).
11933
+ * There used to be a third — "the class was REPOINTED under it", measured
11934
+ * against the class default stamped in the blob (D301). It existed only
11935
+ * because the default was a SECOND write authority layered over the pool and
11936
+ * the two could disagree. With the default deleted the disagreement cannot be
11937
+ * expressed: a location leaving the pool already invalidates every assignment
11938
+ * naming it, and the planner then moves those keys with `reason: 'gone'`.
12023
11939
  */
12024
- async assignedLocation(deviceId, profile, pool, authority) {
11940
+ async assignedLocation(deviceId, profile, pool) {
12025
11941
  let blob;
12026
11942
  try {
12027
11943
  blob = await readPlacementState(this.deps.state);
@@ -12043,23 +11959,28 @@ var PlacementService = class {
12043
11959
  }
12044
11960
  if (assignedId === void 0) return null;
12045
11961
  const location = pool.find((l) => l.id === assignedId && !l.readOnly) ?? null;
12046
- if (location === null) return null;
12047
- const type = placementTypeForProfile(profile);
12048
- const target = authority.get(type);
12049
- const stamped = blob.defaultLocations[type];
12050
- if (target === void 0 || stamped === void 0) return location;
12051
- if (stamped === target || location.id === target) return location;
12052
- this.deps.logger.warn("recorder placement: stored assignment INVALIDATED — this recordings class was repointed and the assignment still names the location it was repointed away from", {
11962
+ if (location === null) this.deps.logger.info("recorder placement: stored assignment INVALIDATED — its location is not a write target any more (disabled, read-only, or gone). Resolving fresh from the pool.", {
12053
11963
  tags: { deviceId },
12054
11964
  meta: {
12055
11965
  profile,
12056
- assignedLocationId: location.id,
12057
- repointedFrom: stamped,
12058
- repointedTo: target,
12059
- locationType: type
11966
+ assignedLocationId: assignedId,
11967
+ poolLocationIds: pool.filter((l) => !l.readOnly).map((l) => l.id)
12060
11968
  }
12061
11969
  });
12062
- return null;
11970
+ return location;
11971
+ }
11972
+ /** The pool, with each member's measured headroom attached. One capacity read
11973
+ * per location; used only on the bootstrap path, never per segment. */
11974
+ async withHeadroom(pool) {
11975
+ const candidates = await this.candidatesFor(pool);
11976
+ const byId = new Map(candidates.map((c) => [c.locationId, c.headroomBytes]));
11977
+ return pool.map((location) => {
11978
+ const headroomBytes = byId.get(location.id);
11979
+ return headroomBytes === void 0 ? location : {
11980
+ ...location,
11981
+ headroomBytes
11982
+ };
11983
+ });
12063
11984
  }
12064
11985
  /**
12065
11986
  * Re-plan the (camera, profile) pairs in `work` over the current locations.
@@ -12081,7 +12002,6 @@ var PlacementService = class {
12081
12002
  */
12082
12003
  async recompute(work) {
12083
12004
  const pool = this.pool();
12084
- const authority = authorityLocations(pool);
12085
12005
  const candidates = await this.candidatesFor(pool);
12086
12006
  const demands = work.map((item) => ({
12087
12007
  key: assignmentKey(item.deviceId, item.profile),
@@ -12098,17 +12018,7 @@ var PlacementService = class {
12098
12018
  } });
12099
12019
  return NO_PLAN;
12100
12020
  }
12101
- const stamped = new Map(Object.entries(blob.defaultLocations));
12102
- const repoints = this.detectRepoints(blob.assignments, stamped, authority, blob.pins);
12103
- if (repoints.length === 0) this.repointAnnounced.clear();
12104
- for (const repoint of repoints) this.announceRepoint(repoint);
12105
- const repointed = /* @__PURE__ */ new Map();
12106
- for (const repoint of repoints) for (const key of repoint.keys) repointed.set(key, repoint.to);
12107
- const current = applyPlacementPass(blob.assignments, {
12108
- repointed,
12109
- planned: /* @__PURE__ */ new Map(),
12110
- defaultLocations: authority
12111
- });
12021
+ const current = new Map(Object.entries(blob.assignments));
12112
12022
  const evacuate = new Set(candidates.filter((c) => c.headroomBytes <= 0).map((c) => c.locationId));
12113
12023
  for (const location of pool) if (location.readOnly) evacuate.add(location.id);
12114
12024
  const plan = planPlacement({
@@ -12118,13 +12028,9 @@ var PlacementService = class {
12118
12028
  evacuate,
12119
12029
  breakTie: (tied) => this.rotation.next(tied)
12120
12030
  });
12121
- const pass = {
12122
- repointed,
12123
- planned: overlayPins(plan.assignments, blob.pins),
12124
- defaultLocations: authority
12125
- };
12126
- this.reportDivergence(applyPlacementPass(blob.assignments, pass), authority);
12127
- if (plan.changes.length === 0 && repointed.size === 0 && sameStamp(stamped, authority)) return plan;
12031
+ const pass = { planned: overlayPins(plan.assignments, blob.pins) };
12032
+ this.reportCensus(applyPlacementPass(blob.assignments, pass), pool);
12033
+ if (plan.changes.length === 0) return plan;
12128
12034
  for (const change of plan.changes) {
12129
12035
  const deviceId = Number(change.key.split(":")[0]);
12130
12036
  this.deps.logger.info("recorder placement: plan moves a profile to another location", {
@@ -12148,129 +12054,60 @@ var PlacementService = class {
12148
12054
  return plan;
12149
12055
  }
12150
12056
  /**
12151
- * Which recordings classes were REPOINTED under the stored plan, and which of
12152
- * its keys that invalidates.
12153
- *
12154
- * PURE. A class is repointed when the blob stamped an authority for it and
12155
- * the authority now is a different location. A class with NO stamp is not a
12156
- * repoint — it is the first pass after this shipped, and inferring a repoint
12157
- * from an absent stamp would throw away every deliberate assignment on the
12158
- * first tick after a deploy.
12159
- */
12160
- detectRepoints(stored, stamped, authority, pins) {
12161
- const out = [];
12162
- for (const [type, to] of authority) {
12163
- const from = stamped.get(type);
12164
- if (from === void 0 || from === to) continue;
12165
- const keys = [];
12166
- for (const [key, locationId] of Object.entries(stored)) {
12167
- const [devicePart, profilePart] = key.split(":");
12168
- if (placementTypeForProfile(profilePart ?? "") !== type) continue;
12169
- if (locationId === to) continue;
12170
- if (type === "recordings" && devicePart !== void 0 && pins[devicePart] !== void 0) continue;
12171
- keys.push(key);
12172
- }
12173
- out.push({
12174
- type,
12175
- from,
12176
- to,
12177
- keys
12178
- });
12179
- }
12180
- return out;
12181
- }
12182
- /**
12183
- * One line PER CAMERA (a repoint is asked per-camera: "did 3839 move?"), plus
12184
- * one audit row per class. A repoint is a rare operator event, so the volume
12185
- * is one burst of fleet-size, not a stream.
12057
+ * WHERE THE FLEET WRITES — one line per (class, location), always with its
12058
+ * denominator (D277).
12186
12059
  *
12187
- * Announced ONCE per episode: the detection is a comparison against the
12188
- * stamp, so a write that fails leaves the stamp behind and the next tick sees
12189
- * the same repoint again. Without the latch, a settings door that accepts
12190
- * reads and refuses writes would repeat the whole burst every 30 s.
12191
- */
12192
- announceRepoint(repoint) {
12193
- const episode = `${repoint.type}:${repoint.from}->${repoint.to}`;
12194
- if (this.repointAnnounced.has(episode)) return;
12195
- this.repointAnnounced.add(episode);
12196
- for (const key of repoint.keys) {
12197
- const deviceId = Number(key.split(":")[0]);
12198
- this.deps.logger.warn("recorder placement: recordings class REPOINTED — moving this assignment onto the class`s new authority location", {
12199
- ...Number.isFinite(deviceId) ? { tags: { deviceId } } : {},
12200
- meta: {
12201
- key,
12202
- locationType: repoint.type,
12203
- repointedFrom: repoint.from,
12204
- repointedTo: repoint.to
12205
- }
12206
- });
12207
- }
12208
- this.deps.opsLog.append({
12209
- op: "relocate",
12210
- reason: "operator",
12211
- deviceId: null,
12212
- itemsAffected: repoint.keys.length,
12213
- bytesReclaimed: 0,
12214
- actor: "system",
12215
- detail: `placement: ${repoint.type} repointed ${repoint.from} → ${repoint.to}; ${repoint.keys.length.toString()} assignment(s) moved (${repoint.keys.join(", ")})`
12216
- });
12217
- }
12218
- /**
12219
- * Say which (camera, profile) the plan writes somewhere other than its class
12220
- * authority — per camera, WITH its denominator (D277).
12221
- *
12222
- * This is not an error path. The planner spreads a class across its pool on
12223
- * purpose, and a location with headroom is a legitimate target. What was
12224
- * wrong is that a deliberate spread and a leftover from a repoint nobody
12225
- * noticed produced the *same* absence of output: `recordDecision` audits only
12226
- * a CHANGE, so a camera that keeps using its old assignment writes no row and
12227
- * no line, forever. An operator read "migration done" while two cameras were
12228
- * still filling the disk they were migrating off.
12060
+ * This replaced the class-authority divergence report (D301) when the default
12061
+ * was deleted: with every enabled location of a class a legitimate target,
12062
+ * "this camera does not write to THE location" is not a fact any more, while
12063
+ * "recordings: 9 of 13 on recordings:default, 4 on recordings:hgst" is
12064
+ * exactly the question an operator asks after enabling a second disk. A pool
12065
+ * member holding ZERO assignments is listed too — an enabled disk nothing
12066
+ * lands on is the failure this whole model exists to make visible.
12229
12067
  *
12230
- * Fires on a CHANGE of the divergent set, and otherwise at most once per
12231
- * {@link PLACEMENT_DIVERGENCE_REPORT_MS}. An all-aligned fleet says nothing —
12232
- * until it stops being all-aligned, and again when it recovers.
12068
+ * Fires on a CHANGE of the distribution, and otherwise at most once per
12069
+ * {@link PLACEMENT_CENSUS_REPORT_MS}.
12233
12070
  */
12234
- reportDivergence(assignments, authority) {
12235
- const divergent = [];
12071
+ reportCensus(assignments, pool) {
12072
+ const counts = /* @__PURE__ */ new Map();
12073
+ const perClass = /* @__PURE__ */ new Map();
12074
+ for (const location of pool) if (!location.readOnly) counts.set(location.id, 0);
12236
12075
  for (const [key, locationId] of assignments) {
12237
- const profile = key.split(":")[1] ?? "";
12238
- const authorityLocationId = authority.get(placementTypeForProfile(profile));
12239
- if (authorityLocationId === void 0 || authorityLocationId === locationId) continue;
12240
- divergent.push({
12241
- key,
12242
- deviceId: Number(key.split(":")[0]),
12243
- profile,
12244
- locationId,
12245
- authorityLocationId
12246
- });
12076
+ counts.set(locationId, (counts.get(locationId) ?? 0) + 1);
12077
+ const type = placementTypeForProfile(key.split(":")[1] ?? "");
12078
+ perClass.set(type, (perClass.get(type) ?? 0) + 1);
12247
12079
  }
12248
- const signature = divergent.map((d) => `${d.key}=${d.locationId}`).toSorted().join(",");
12249
- const previous = this.divergenceSignature;
12080
+ const typeOf = new Map(pool.map((location) => [location.id, location.type]));
12081
+ const lines = [...counts.entries()].toSorted(([a], [b]) => a.localeCompare(b)).map(([locationId, count]) => {
12082
+ const type = typeOf.get(locationId) ?? locationId.split(":")[0] ?? locationId;
12083
+ return {
12084
+ locationId,
12085
+ type,
12086
+ assignments: count,
12087
+ ofClass: perClass.get(type) ?? 0
12088
+ };
12089
+ });
12090
+ const signature = lines.map((line) => `${line.locationId}=${String(line.assignments)}`).join(",");
12250
12091
  const now = this.now;
12251
- const due = this.divergenceReportedAt === null || now - this.divergenceReportedAt >= 36e5;
12252
- if (signature === previous && !due) return;
12253
- this.divergenceSignature = signature;
12254
- this.divergenceReportedAt = now;
12255
- if (divergent.length === 0) {
12256
- if (previous !== null && previous !== "") this.deps.logger.info("recorder placement: every assignment now writes to its class authority location", { meta: { assignments: assignments.size } });
12257
- return;
12092
+ const due = this.censusReportedAt === null || now - this.censusReportedAt >= 36e5;
12093
+ if (signature === this.censusSignature && !due) return;
12094
+ this.censusSignature = signature;
12095
+ this.censusReportedAt = now;
12096
+ if (lines.length === 0) return;
12097
+ this.deps.logger.info("recorder placement: census — which locations the fleet writes to", { meta: { locations: lines.map((line) => ({
12098
+ locationId: line.locationId,
12099
+ type: line.type,
12100
+ assignments: line.assignments,
12101
+ ofClass: line.ofClass
12102
+ })) } });
12103
+ for (const line of lines) {
12104
+ if (line.assignments > 0) continue;
12105
+ this.deps.logger.warn("recorder placement: this location is ENABLED but holds no assignment — nothing writes to it", { meta: {
12106
+ locationId: line.locationId,
12107
+ type: line.type,
12108
+ ofClass: line.ofClass
12109
+ } });
12258
12110
  }
12259
- this.deps.logger.warn("recorder placement: assignments write somewhere other than their class authority location", { meta: {
12260
- divergent: divergent.length,
12261
- assignments: assignments.size,
12262
- authority: Object.fromEntries(authority)
12263
- } });
12264
- for (const entry of divergent) this.deps.logger.warn("recorder placement: this profile does NOT write to its class authority location", {
12265
- ...Number.isFinite(entry.deviceId) ? { tags: { deviceId: entry.deviceId } } : {},
12266
- meta: {
12267
- profile: entry.profile,
12268
- locationId: entry.locationId,
12269
- authorityLocationId: entry.authorityLocationId,
12270
- divergent: divergent.length,
12271
- assignments: assignments.size
12272
- }
12273
- });
12274
12111
  }
12275
12112
  /** Bound one addon-store round trip. See {@link PLACEMENT_STORE_TIMEOUT_MS}. */
12276
12113
  bounded(promise, label) {
@@ -12451,13 +12288,13 @@ var PlacementService = class {
12451
12288
  * Persist the assignment and audit it — but only when it actually changed.
12452
12289
  * The steady state (every attach after the first) writes nothing.
12453
12290
  *
12454
- * The per-attach line carries `authorityLocationId` and `divergent` so a
12455
- * single camera's line answers "is 3839 writing where the class default
12456
- * says?" on its own. Without it, a camera pinned to a location the class was
12457
- * repointed away from logged `reason: 'resolved'` — byte-identical to a
12458
- * camera correctly on the current default.
12291
+ * The per-attach line carries the camera's location and the one it replaced,
12292
+ * tagged with `deviceId`, so "where does 3839 write, and did it move?" is
12293
+ * answerable per camera. Which locations the FLEET is spread over is the
12294
+ * census's job (D383) — there is no class authority for a single line to be
12295
+ * measured against any more.
12459
12296
  */
12460
- async recordDecision(deviceId, profile, location, verbatim, authorityLocationId) {
12297
+ async recordDecision(deviceId, profile, location, verbatim) {
12461
12298
  const key = assignmentKey(deviceId, profile);
12462
12299
  let previous;
12463
12300
  try {
@@ -12478,8 +12315,6 @@ var PlacementService = class {
12478
12315
  locationId: location.id,
12479
12316
  root: location.root,
12480
12317
  previousLocationId: previous ?? null,
12481
- authorityLocationId,
12482
- divergent: authorityLocationId !== null && authorityLocationId !== location.id,
12483
12318
  reason: verbatim ? "resolved" : "failover"
12484
12319
  }
12485
12320
  });
@@ -15377,7 +15212,7 @@ var RecordingController = class {
15377
15212
  tag: `recorder:${deviceId}/${profile}`
15378
15213
  }, nodePin(this.deps.ownerNodeId)), ATTACH_RPC_TIMEOUT_MS, "getStreamWithCodec"));
15379
15214
  const streamAcquiredMs = this.now();
15380
- const rtspUrl = withRecordingIntent(source.url);
15215
+ const rtspUrl = withRecordingIntent(source.url, RECORDING_PRE_ROLL_REQUEST_MS);
15381
15216
  this.attachSeq++;
15382
15217
  const consumerTag = `recorder/${deviceId}/${profile}/${this.attachSeq}`;
15383
15218
  if (rtspUrl === source.url) this.deps.logger.info("recorder: dial carries no recording intent — no pre-roll", {