@camstack/addon-post-analysis 1.2.333 → 1.2.335

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.
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-BEh9LdQS.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-BxUHFGUg.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -10754,7 +10754,8 @@ var RAW_CLASS_STEP_ID = "detector:raw-class";
10754
10754
  * Choose the label CANDIDATE a detection offers, and nothing more.
10755
10755
  *
10756
10756
  * An enrichment label (identity, plate text, species, sub-class) takes
10757
- * precedence; otherwise the root detector's raw class, when it ships at all.
10757
+ * precedence; otherwise the detector's score-0 linked label (D715); otherwise
10758
+ * the root detector's raw class, when it ships at all.
10758
10759
  * Whether the candidate may be WRITTEN — and into which slot — is
10759
10760
  * {@link resolveTieredLabelWrite}'s decision, in `label-tier.ts`. This split is
10760
10761
  * deliberate: the macro-class refusal that shipped on 2026-08-07 used to live
@@ -10773,6 +10774,15 @@ function resolveDetectionLabelCandidate(input) {
10773
10774
  ...top.modelId !== void 0 ? { modelId: top.modelId } : {},
10774
10775
  decidedAt: input.decidedAt
10775
10776
  };
10777
+ const linked = input.labels?.find((l) => l.score === 0 && require_clip_model_registry.linkedStepOf(l.stepId) !== void 0 && l.label !== input.className);
10778
+ if (linked !== void 0 && linked.stepId !== void 0) return {
10779
+ text: linked.label,
10780
+ ...linked.tier !== void 0 ? { tier: linked.tier } : {},
10781
+ score: 0,
10782
+ stepId: linked.stepId,
10783
+ ...linked.modelId !== void 0 ? { modelId: linked.modelId } : {},
10784
+ decidedAt: input.decidedAt
10785
+ };
10776
10786
  const raw = input.originalClass;
10777
10787
  if (raw === void 0 || raw === input.className) return void 0;
10778
10788
  return {
@@ -13510,6 +13520,7 @@ function incomingFromSystemEvent(event, lookup) {
13510
13520
  return update === null ? null : incomingFromAddonUpdates(facts, [update]);
13511
13521
  }
13512
13522
  case "system.restart-completed": return incomingFromServerUpdated(facts, data);
13523
+ case "system.restarting": return incomingFromNodeRestarting(facts, data);
13513
13524
  case "recording.export.completed": return incomingFromExportCompleted(facts, data);
13514
13525
  case "backup.run-completed": return incomingFromBackupCompleted(facts, data);
13515
13526
  default: return null;
@@ -13655,7 +13666,7 @@ function incomingFromServerUpdated(facts, data) {
13655
13666
  const packageName = stringField(data, "packageName") ?? "@camstack/server";
13656
13667
  const fromVersion = stringField(data, "fromVersion");
13657
13668
  const toVersion = stringField(data, "toVersion") ?? versionFromRequestedBy(requestedBy);
13658
- if (!(kind === "framework-update" || kind === "framework-bulk-update" || requestedBy !== void 0 && requestedBy.startsWith("server-update:") && toVersion !== void 0)) return null;
13669
+ if (!restartIsUpdate(kind, requestedBy, toVersion)) return null;
13659
13670
  const nodeId = stringField(data, "nodeId") ?? "hub";
13660
13671
  const bodyKey = toVersion === void 0 ? "system.server-updated.body.plain" : fromVersion === void 0 ? "system.server-updated.body.toOnly" : "system.server-updated.body";
13661
13672
  return systemSubject({
@@ -13676,6 +13687,69 @@ function incomingFromServerUpdated(facts, data) {
13676
13687
  });
13677
13688
  }
13678
13689
  /**
13690
+ * Is this restart marker (`PendingRestartMarkerPayload`) an UPDATE?
13691
+ *
13692
+ * A PLAIN node restart travels under the same `server-update:` reason prefix
13693
+ * (`RootUpdateService.restartNode` → `server-update: manual restart`), so the
13694
+ * prefix alone is not evidence of an update — it needs a version, or a kind
13695
+ * that says so. Announcing "Server aggiornato" for a restart is a lie the
13696
+ * operator cannot check. ONE predicate for both halves of a restart (D723): the
13697
+ * `system.restarting` announcement and the `system.restart-completed` landing
13698
+ * must never disagree about whether the same marker was an update.
13699
+ */
13700
+ function restartIsUpdate(kind, requestedBy, toVersion) {
13701
+ return kind === "framework-update" || kind === "framework-bulk-update" || requestedBy !== void 0 && requestedBy.startsWith("server-update:") && toVersion !== void 0;
13702
+ }
13703
+ /**
13704
+ * A node announcing its OWN restart, before it exits (D723).
13705
+ *
13706
+ * The same `system.restarting` payload becomes one of two kinds, split by
13707
+ * {@link restartIsUpdate}: `node-updating` names the node and both versions
13708
+ * (when known), `node-restarting` names only the node — a plain restart comes
13709
+ * back as it was. A payload without `nodeId` is the hub's own (the hub's
13710
+ * emit predates the field, exactly as `system.restart-completed` defaults).
13711
+ *
13712
+ * The `node-updating` subject is keyed like `server-updated`'s
13713
+ * (`<node>:<toVersion>`): it is what the delivery gate compares to decide that
13714
+ * an announcement which drained only AFTER the landing is stale
13715
+ * (`restart-supersede.ts`).
13716
+ */
13717
+ function incomingFromNodeRestarting(facts, data) {
13718
+ const kind = stringField(data, "kind");
13719
+ const requestedBy = stringField(data, "requestedBy");
13720
+ const nodeId = stringField(data, "nodeId") ?? "hub";
13721
+ const toVersion = stringField(data, "toVersion") ?? versionFromRequestedBy(requestedBy);
13722
+ if (!restartIsUpdate(kind, requestedBy, toVersion)) return systemSubject({
13723
+ facts,
13724
+ kind: "node-restarting",
13725
+ subject: `node-restart:${nodeId}:${numberField(data, "requestedAt") ?? facts.timestamp}`,
13726
+ titleKey: "system.node-restarting.title",
13727
+ bodyKey: "system.node-restarting.body",
13728
+ textParams: { nodeId },
13729
+ nodeId,
13730
+ nodeIds: [nodeId]
13731
+ });
13732
+ const packageName = stringField(data, "packageName") ?? "@camstack/server";
13733
+ const fromVersion = stringField(data, "fromVersion");
13734
+ const bodyKey = toVersion === void 0 ? "system.node-updating.body.plain" : fromVersion === void 0 ? "system.node-updating.body.toOnly" : "system.node-updating.body";
13735
+ return systemSubject({
13736
+ facts,
13737
+ kind: "node-updating",
13738
+ subject: `node-updating:${nodeId}:${toVersion ?? "unknown"}`,
13739
+ titleKey: "system.node-updating.title",
13740
+ bodyKey,
13741
+ textParams: {
13742
+ packageName,
13743
+ nodeId,
13744
+ ...fromVersion !== void 0 ? { fromVersion } : {},
13745
+ ...toVersion !== void 0 ? { toVersion } : {}
13746
+ },
13747
+ packageName,
13748
+ nodeId,
13749
+ nodeIds: [nodeId]
13750
+ });
13751
+ }
13752
+ /**
13679
13753
  * A finished backup RUN.
13680
13754
  *
13681
13755
  * Subject is the run, not the destination: `backup.completed` fires once per
@@ -15504,6 +15578,12 @@ var en_default = {
15504
15578
  "system.server-updated.body": "{{packageName}} on {{nodeId}} is now {{toVersion}} (was {{fromVersion}})",
15505
15579
  "system.server-updated.body.toOnly": "{{packageName}} on {{nodeId}} is now {{toVersion}}",
15506
15580
  "system.server-updated.body.plain": "{{packageName}} on {{nodeId}} was updated",
15581
+ "system.node-restarting.title": "Node restarting — {{nodeId}}",
15582
+ "system.node-restarting.body": "{{nodeId}} is restarting and will be back shortly",
15583
+ "system.node-updating.title": "Node updating — {{nodeId}}",
15584
+ "system.node-updating.body": "{{packageName}} on {{nodeId}} is updating {{fromVersion}} → {{toVersion}} and restarts now",
15585
+ "system.node-updating.body.toOnly": "{{packageName}} on {{nodeId}} is updating to {{toVersion}} and restarts now",
15586
+ "system.node-updating.body.plain": "{{packageName}} on {{nodeId}} is updating and restarts now",
15507
15587
  "system.export-completed.title": "Export ready",
15508
15588
  "system.export-completed.body": "The footage export is ready",
15509
15589
  "system.export-completed.body.titled": "{{title}} is ready",
@@ -15860,6 +15940,12 @@ var it_default = {
15860
15940
  "system.server-updated.body": "{{packageName}} sul nodo {{nodeId}} è ora {{toVersion}} (era {{fromVersion}})",
15861
15941
  "system.server-updated.body.toOnly": "{{packageName}} sul nodo {{nodeId}} è ora {{toVersion}}",
15862
15942
  "system.server-updated.body.plain": "{{packageName}} sul nodo {{nodeId}} è stato aggiornato",
15943
+ "system.node-restarting.title": "Nodo in riavvio — {{nodeId}}",
15944
+ "system.node-restarting.body": "{{nodeId}} si sta riavviando e tornerà a breve",
15945
+ "system.node-updating.title": "Nodo in aggiornamento — {{nodeId}}",
15946
+ "system.node-updating.body": "{{packageName}} sul nodo {{nodeId}} si aggiorna da {{fromVersion}} a {{toVersion}} e si riavvia ora",
15947
+ "system.node-updating.body.toOnly": "{{packageName}} sul nodo {{nodeId}} si aggiorna a {{toVersion}} e si riavvia ora",
15948
+ "system.node-updating.body.plain": "{{packageName}} sul nodo {{nodeId}} si sta aggiornando e si riavvia ora",
15863
15949
  "system.export-completed.title": "Export pronto",
15864
15950
  "system.export-completed.body": "L'export del filmato è pronto",
15865
15951
  "system.export-completed.body.titled": "{{title}} è pronto",
@@ -18312,6 +18398,8 @@ function systemEventSubject(kind, scope) {
18312
18398
  if (kind === "wrapper-update-available") return `wrapper:${scope.nodeId ?? "hub"}`;
18313
18399
  if (kind === "addon-updated") return `packages:addon-updated:${scope.packageName ?? "pkg"}`;
18314
18400
  if (kind === "server-updated") return `server-updated:${scope.nodeId ?? "hub"}`;
18401
+ if (kind === "node-restarting") return `node-restart:${scope.nodeId ?? "hub"}:test`;
18402
+ if (kind === "node-updating") return `node-updating:${scope.nodeId ?? "hub"}:1.0.1`;
18315
18403
  if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
18316
18404
  if (kind === "backup-completed") return "backup:test";
18317
18405
  return `alarm:${scope.deviceId ?? 0}`;
@@ -29418,6 +29506,11 @@ function recordToRow$1(key, data) {
29418
29506
  */
29419
29507
  var NC_OUTBOX_COLLECTION = "notification-center:outbox";
29420
29508
  /**
29509
+ * The bound on one recent-rows read (`tryQueryRecentPersisted`: the boot hydration and the
29510
+ * cooldown reseed). Newest first, so a read that hits it drops the OLDEST rows — and says so.
29511
+ */
29512
+ var NC_OUTBOX_RECENT_QUERY_LIMIT = 1e4;
29513
+ /**
29421
29514
  * @durable class=ledger owner=notification-center
29422
29515
  * write="one row, key `watermark`: the boot-reconcile cursor. Advanced to `now` at the
29423
29516
  * end of every `reconcile()` (each boot) and every 15th drain tick (~30 s) while
@@ -29427,11 +29520,6 @@ var NC_OUTBOX_COLLECTION = "notification-center:outbox";
29427
29520
  * a bounded re-scan: the reconcile falls back to the 15-minute window and the replay
29428
29521
  * is idempotent through the outbox dedup id."
29429
29522
  */
29430
- /**
29431
- * The bound on one recent-rows read (`tryQueryRecentPersisted`: the boot hydration and the
29432
- * cooldown reseed). Newest first, so a read that hits it drops the OLDEST rows — and says so.
29433
- */
29434
- var NC_OUTBOX_RECENT_QUERY_LIMIT = 1e4;
29435
29523
  var NC_META_COLLECTION = "notification-center:meta";
29436
29524
  var NC_WATERMARK_KEY = "watermark";
29437
29525
  var NC_OUTBOX_COLUMNS = [
@@ -31714,6 +31802,50 @@ function buildServerUpdateButtons(input) {
31714
31802
  }];
31715
31803
  }
31716
31804
  //#endregion
31805
+ //#region src/notification-center/restart-supersede.ts
31806
+ /** How many landings are remembered — one per node per update, so a fleet's worth. */
31807
+ var NC_UPDATE_LANDINGS_MAX = 256;
31808
+ function landingKey(nodeId, toVersion) {
31809
+ return `${nodeId}\u0000${toVersion}`;
31810
+ }
31811
+ var NcUpdateLandings = class {
31812
+ max;
31813
+ /** Insertion-ordered: the oldest landing is the first one evicted. */
31814
+ landed = /* @__PURE__ */ new Map();
31815
+ constructor(max = NC_UPDATE_LANDINGS_MAX) {
31816
+ this.max = max;
31817
+ }
31818
+ /** Feed every system subject the intake accepted; only `server-updated` is kept. */
31819
+ observe(system, atMs) {
31820
+ if (system.kind !== "server-updated") return;
31821
+ const nodeId = system.nodeId;
31822
+ const toVersion = system.textParams?.["toVersion"];
31823
+ if (nodeId === void 0 || toVersion === void 0) return;
31824
+ const key = landingKey(nodeId, toVersion);
31825
+ this.landed.delete(key);
31826
+ this.landed.set(key, atMs);
31827
+ while (this.landed.size > this.max) {
31828
+ const oldest = this.landed.keys().next();
31829
+ if (oldest.done === true) break;
31830
+ this.landed.delete(oldest.value);
31831
+ }
31832
+ }
31833
+ /** The landing that makes this row stale, or `null` when it should be sent. */
31834
+ supersedes(entry) {
31835
+ const system = entry.payload.subject.systemEvent;
31836
+ if (system === void 0 || system.kind !== "node-updating") return null;
31837
+ const nodeId = system.nodeId;
31838
+ const toVersion = system.textParams?.["toVersion"];
31839
+ if (nodeId === void 0 || toVersion === void 0) return null;
31840
+ const landedAt = this.landed.get(landingKey(nodeId, toVersion));
31841
+ return landedAt === void 0 ? null : {
31842
+ nodeId,
31843
+ toVersion,
31844
+ landedAt
31845
+ };
31846
+ }
31847
+ };
31848
+ //#endregion
31717
31849
  //#region src/notification-center/snooze-digest.ts
31718
31850
  /** `14:03` in the node's local zone — a digest is read by a person, at home. */
31719
31851
  function clock(ms) {
@@ -38690,6 +38822,8 @@ var NotificationCenter = class {
38690
38822
  deviceAlerts;
38691
38823
  /** The scheduled STATE SUMMARY of system rules (D647): its ledgers and its tick. */
38692
38824
  stateSummary;
38825
+ /** Every `server-updated` heard, so a stale `node-updating` is not sent after it (D723). */
38826
+ updateLandings = new NcUpdateLandings();
38693
38827
  /** States the `deviceState` rule gate reads. Refreshed on the reload tick;
38694
38828
  * read with no I/O on the persist hook. */
38695
38829
  deviceStates;
@@ -39709,7 +39843,7 @@ var NotificationCenter = class {
39709
39843
  this.outbox = new NcOutbox({
39710
39844
  store: deps.store,
39711
39845
  logger: this.logger.child("outbox"),
39712
- deliver: (entry) => this.dispatcher.deliver(entry),
39846
+ deliver: (entry) => this.deliverUnlessSuperseded(entry),
39713
39847
  ...deps.now !== void 0 ? { now: deps.now } : {}
39714
39848
  });
39715
39849
  this.timelapseRules = new TimelapseStore({
@@ -41135,6 +41269,7 @@ var NotificationCenter = class {
41135
41269
  }
41136
41270
  const system = incoming.subject.systemEvent;
41137
41271
  if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
41272
+ if (system !== void 0) this.updateLandings.observe(system, this.now());
41138
41273
  const observation = system !== void 0 ? ledgeredLiveness(system) : null;
41139
41274
  if (observation !== null) {
41140
41275
  const previous = this.liveness.stateOf(observation.subject);
@@ -41170,6 +41305,29 @@ var NotificationCenter = class {
41170
41305
  }
41171
41306
  this.consumeEvent(incoming);
41172
41307
  }
41308
+ /**
41309
+ * The outbox's executor: the dispatcher, unless the row is a `node-updating`
41310
+ * whose update has already LANDED (D723, `restart-supersede.ts`). Such a row
41311
+ * drained after the hub's own reboot and would read "about to update" next to
41312
+ * "updated". Dropped benignly (the row ends `sent`, like a snooze), and said.
41313
+ */
41314
+ deliverUnlessSuperseded(entry) {
41315
+ const superseded = this.updateLandings.supersedes(entry);
41316
+ if (superseded === null) return this.dispatcher.deliver(entry);
41317
+ this.logger.info("node-updating not sent — the update already landed", {
41318
+ tags: { deviceId: entry.deviceId },
41319
+ meta: {
41320
+ nodeId: superseded.nodeId,
41321
+ toVersion: superseded.toVersion,
41322
+ landedAt: superseded.landedAt,
41323
+ ruleId: entry.ruleId,
41324
+ targetId: entry.targetId,
41325
+ outboxId: entry.id,
41326
+ createdAt: entry.createdAt
41327
+ }
41328
+ });
41329
+ return Promise.resolve({ ok: true });
41330
+ }
41173
41331
  buildProvider() {
41174
41332
  return {
41175
41333
  listRules: async () => ({ rules: [...this.rules.list()] }),
@@ -48935,7 +49093,10 @@ function analyseDebugTrack(inventory) {
48935
49093
  }
48936
49094
  }
48937
49095
  if (closed && facts.bestEventId === void 0) out.push(finding("NO_BEST_EVENT", "info", "No representative event was chosen for this track."));
49096
+ const linkedStep = require_clip_model_registry.linkedStepOf(facts.labelStepId);
48938
49097
  if (facts.labelStepId === RAW_CLASS_STEP_ID) out.push(finding("UNSCORED_LABEL", "info", `The label is the detector's raw class (${facts.label ?? facts.className}) — no scored classifier answered for this track.`));
49098
+ else if (linkedStep !== void 0 && (facts.labelScore ?? 0) === 0) out.push(finding("UNSCORED_LABEL", "info", `The label is the root detector's linked label for ${linkedStep} (${facts.label ?? facts.className}), held as a score-0 fallback — ${linkedStep} runs for this camera and no scored answer of it landed.`));
49099
+ else if (linkedStep !== void 0 && facts.labelScore !== void 0 && facts.labelScore < LOW_BEST_SCORE) out.push(finding("LOW_BEST_SCORE", "info", `The label came from the root detector standing in for ${linkedStep}; its score ${facts.labelScore.toFixed(2)} is the DETECTION's confidence, under ${String(LOW_BEST_SCORE)} — a ${linkedStep} threshold has no part in it.`));
48939
49100
  else if (facts.labelScore !== void 0 && facts.labelScore < LOW_BEST_SCORE) out.push(finding("LOW_BEST_SCORE", "info", `Best label score ${facts.labelScore.toFixed(2)} is under ${String(LOW_BEST_SCORE)}.`));
48940
49101
  if (thumbnailProvenance === "standin") out.push(finding("BEST_SHOT_STANDIN", "warn", "Best shot is a stand-in: a face/plate detail crop copied in because no frame of the subject was captured."));
48941
49102
  if (thumbnailProvenance === "raster") out.push(finding("BEST_SHOT_RASTER", "info", "Best shot is a sub-native raster crop of the subject, not a native capture."));
@@ -60844,6 +61005,15 @@ function isIdleTrackerStats(stats) {
60844
61005
  }
60845
61006
  /** Shared empty — a fold is rare and every other frame would allocate. */
60846
61007
  var EMPTY_RIDER_FOLDS = [];
61008
+ /** The steps a detection's linked labels stand in for (D715), or `undefined` for none. */
61009
+ function linkedStepsOf(labels) {
61010
+ if (labels === void 0) return void 0;
61011
+ const steps = labels.flatMap((l) => {
61012
+ const step = require_clip_model_registry.linkedStepOf(l.stepId);
61013
+ return step === void 0 ? [] : [step];
61014
+ });
61015
+ return steps.length > 0 ? steps : void 0;
61016
+ }
60847
61017
  /** The tape observation of a dropped frame: no gate verdict, nothing bound. */
60848
61018
  var DROPPED_FRAME_OBSERVATION = {
60849
61019
  gate: {
@@ -61751,6 +61921,7 @@ var FrameProcessor = class {
61751
61921
  const sourceDetectionId = sourceIdByBbox.get(td.bbox);
61752
61922
  const nativeRecovery = td.matchedThisFrame !== false ? nativeRecoveryByBbox.get(td.bbox) : void 0;
61753
61923
  const birthEvidence = td.matchedThisFrame !== false ? birthEvidenceByBbox.get(td.bbox) : void 0;
61924
+ const linkedSteps = td.matchedThisFrame !== false ? linkedStepsOf(labelsByBbox.get(td.bbox)) : void 0;
61754
61925
  return {
61755
61926
  trackId: td.trackId,
61756
61927
  ...sourceDetectionId !== void 0 ? { sourceDetectionId } : {},
@@ -61776,7 +61947,8 @@ var FrameProcessor = class {
61776
61947
  plateBbox: plate.bbox
61777
61948
  } : {},
61778
61949
  ...nativeRecovery !== void 0 ? { nativeRecovery } : {},
61779
- ...birthEvidence !== void 0 ? { birthEvidence } : {}
61950
+ ...birthEvidence !== void 0 ? { birthEvidence } : {},
61951
+ ...linkedSteps !== void 0 ? { linkedSteps } : {}
61780
61952
  };
61781
61953
  });
61782
61954
  this.lastLabelWriteRefusals = refusals;
@@ -78104,6 +78276,24 @@ var DetailScheduler = class {
78104
78276
  }
78105
78277
  if (this.underMaxPerTrack(state)) state.retryPending = true;
78106
78278
  }
78279
+ /**
78280
+ * The root model the announce says fills this (track, step), if any (D715).
78281
+ * Read from the announce the track was scheduled with.
78282
+ */
78283
+ filledBy(trackId, stepId) {
78284
+ return this.tracks.get(trackId)?.get(stepId)?.announce.filledBy;
78285
+ }
78286
+ /**
78287
+ * The detector already produced this step's effect for the track (D715):
78288
+ * the step is settled for the track's life — no retry, improve or periodic
78289
+ * re-fire. Same terminal state as a confident result.
78290
+ */
78291
+ markFilled(trackId, stepId) {
78292
+ const state = this.tracks.get(trackId)?.get(stepId);
78293
+ if (state === void 0) return;
78294
+ state.sticky = true;
78295
+ state.retryPending = false;
78296
+ }
78107
78297
  onTrackEnded(trackId) {
78108
78298
  this.tracks.delete(trackId);
78109
78299
  }
@@ -78329,9 +78519,37 @@ var TrackDetailDispatcher = class {
78329
78519
  }
78330
78520
  enqueue(deviceId, dev, requests) {
78331
78521
  if (requests.length === 0) return;
78332
- for (const r of requests) dev.queue.push(r);
78522
+ for (const r of requests) {
78523
+ if (this.skipFilled(deviceId, dev, r)) continue;
78524
+ dev.queue.push(r);
78525
+ }
78333
78526
  this.pump(deviceId, dev);
78334
78527
  }
78528
+ /**
78529
+ * D715: the root model fills this step (the announce's `filledBy`) and the
78530
+ * detector named THIS subject for it (a linked label on the track's frame) —
78531
+ * the step's effect already exists, so it is not dispatched. The step is
78532
+ * settled for the track, which is what makes the line below fire once per
78533
+ * (track, step): a settled step is never requested again. A track the
78534
+ * detector did not name (a class outside its vocabulary) is dispatched as
78535
+ * before.
78536
+ */
78537
+ skipFilled(deviceId, dev, req) {
78538
+ const filledBy = dev.scheduler.filledBy(req.trackId, req.stepId);
78539
+ if (filledBy === void 0) return false;
78540
+ if (dev.tracks.get(req.trackId)?.linkedSteps?.includes(req.stepId) !== true) return false;
78541
+ dev.scheduler.markFilled(req.trackId, req.stepId);
78542
+ this.deps.logger.info("detail step skipped — the root detector already filled it", {
78543
+ tags: { deviceId },
78544
+ meta: {
78545
+ trackId: req.trackId,
78546
+ stepId: req.stepId,
78547
+ filledBy,
78548
+ reason: req.reason
78549
+ }
78550
+ });
78551
+ return true;
78552
+ }
78335
78553
  pump(deviceId, dev) {
78336
78554
  while (dev.inFlight < this.maxInFlight && dev.queue.length > 0) {
78337
78555
  const req = dev.queue.shift();
@@ -96851,6 +97069,13 @@ var ANALYTICS_COLLECTIONS = [
96851
97069
  why: "the frame COPIES the dataset owns outright, under a `retrain` prefix no owner cascade and no age sweep walks (D81). Also declares `trackId` as provenance. Rows go only through `deleteFrame`, after their annotations, on operator deselection. NOTE: nothing caps this table or the disk it consumes — that is a deliberate decision, not an oversight, and it is the one collection here whose bound is an operator's habit."
96852
97070
  }
96853
97071
  },
97072
+ {
97073
+ collection: RETRAIN_COLLECTIONS_COLLECTION,
97074
+ classification: {
97075
+ kind: "never-orphaned",
97076
+ why: "the named training corpora an operator CREATES from playback — one metadata row per collection (id, name, timestamps), keyed by nothing that can expire: no trackId, no event id, no device. There is no owner to go away, and no age clock applies, because a collection the operator built and has not opened since is not stale, it is kept. Rows leave only through an explicit operator delete."
97077
+ }
97078
+ },
96854
97079
  {
96855
97080
  collection: SCENE_COLLECTION,
96856
97081
  classification: {
@@ -99243,6 +99468,7 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
99243
99468
  require_clip_model_registry.EventCategory.UpdateAvailable,
99244
99469
  require_clip_model_registry.EventCategory.AddonUpdated,
99245
99470
  require_clip_model_registry.EventCategory.SystemRestartCompleted,
99471
+ require_clip_model_registry.EventCategory.SystemRestarting,
99246
99472
  require_clip_model_registry.EventCategory.RecordingExportCompleted,
99247
99473
  require_clip_model_registry.EventCategory.BackupRunCompleted
99248
99474
  ];
@@ -111471,7 +111697,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
111471
111697
  ...frameHandle !== void 0 ? {
111472
111698
  frameHandle,
111473
111699
  nodeId: frameHandle.nodeId
111474
- } : {}
111700
+ } : {},
111701
+ ...t.linkedSteps !== void 0 ? { linkedSteps: t.linkedSteps } : {}
111475
111702
  };
111476
111703
  if (prevIds.has(t.trackId)) dispatcher.onFrame(deviceId, t.trackId, steps, detailFrame, result.timestamp);
111477
111704
  else dispatcher.onTrackStarted(deviceId, t.trackId, t.className, steps, detailFrame, result.timestamp);