@camstack/addon-post-analysis 1.2.347 → 1.2.349

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 (15) hide show
  1. package/dist/{clip-model-registry-BftmbgcV.mjs → clip-model-registry-CC-BJYiK.mjs} +1786 -148
  2. package/dist/{clip-model-registry--UsI8Ud4.js → clip-model-registry-DI12tTgY.js} +1786 -148
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DuQPp3dT.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BYsMflvm.mjs} +3 -3
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Du_tzgi2.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CQOJKiwa.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-BDQ6oQ7b.mjs → hostInit-Cpi67Icm.mjs} +3 -3
  10. package/dist/pipeline-analytics/index.js +575 -259
  11. package/dist/pipeline-analytics/index.mjs +575 -259
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-4Fu7jNn9.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D5IzYeGQ.mjs +0 -26
@@ -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--UsI8Ud4.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-DI12tTgY.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");
@@ -13391,14 +13391,19 @@ function incomingFromSystemEvent(event, lookup) {
13391
13391
  case "agent.offline": {
13392
13392
  const nodeId = stringField(data, "agentId");
13393
13393
  if (nodeId === void 0) return null;
13394
- const kind = event.category.endsWith(".online") ? "node-online" : "node-offline";
13394
+ const online = event.category.endsWith(".online");
13395
+ const kind = online ? "node-online" : "node-offline";
13396
+ const reason = online ? void 0 : stringField(data, "reason");
13395
13397
  return systemSubject({
13396
13398
  facts,
13397
13399
  kind,
13398
13400
  subject: `node:${nodeId}`,
13399
13401
  titleKey: `system.${kind}.title`,
13400
- bodyKey: `system.${kind}.body.agent`,
13401
- textParams: { nodeId },
13402
+ bodyKey: reason === "connection-lost" || reason === "shutdown" ? `system.${kind}.body.agent.${reason}` : `system.${kind}.body.agent`,
13403
+ textParams: {
13404
+ nodeId,
13405
+ nodeName: stringField(data, "nodeName") ?? nodeId
13406
+ },
13402
13407
  nodeId
13403
13408
  });
13404
13409
  }
@@ -15484,10 +15489,13 @@ var en_default = {
15484
15489
  "system.stream-offline.title": "Stream offline — {{deviceName}}",
15485
15490
  "system.stream-offline.body": "Stream for camera {{deviceName}} is offline",
15486
15491
  "system.stream-offline.body.profiled": "{{profile}} stream for camera {{deviceName}} is offline",
15487
- "system.node-online.title": "Node online — {{nodeId}}",
15488
- "system.node-online.body.agent": "Agent {{nodeId}} is online",
15489
- "system.node-offline.title": "Node offline — {{nodeId}}",
15490
- "system.node-offline.body.agent": "Agent {{nodeId}} is offline",
15492
+ "system.node-online.title": "Node online — {{nodeName}}",
15493
+ "system.node-online.body.agent": "Agent {{nodeName}} is online",
15494
+ "system.node-online.body.agent.after": "Agent {{nodeName}} is back online after {{offlineFor}} offline",
15495
+ "system.node-offline.title": "Node offline — {{nodeName}}",
15496
+ "system.node-offline.body.agent": "Agent {{nodeName}} is offline",
15497
+ "system.node-offline.body.agent.connection-lost": "Agent {{nodeName}} lost its connection to the hub",
15498
+ "system.node-offline.body.agent.shutdown": "Agent {{nodeName}} shut down",
15491
15499
  "system.node-inference-unavailable.title": "Node cannot infer — {{nodeId}}",
15492
15500
  "system.node-inference-unavailable.body": "Node {{nodeId}} can no longer run inference — every enabled device is unavailable ({{devices}}). It will not receive cameras until one returns.",
15493
15501
  "system.addon-crash-loop.title": "Addon crash-loop — {{nodeId}}",
@@ -15846,10 +15854,13 @@ var it_default = {
15846
15854
  "system.stream-offline.title": "Stream offline — {{deviceName}}",
15847
15855
  "system.stream-offline.body": "Lo stream della camera {{deviceName}} è offline",
15848
15856
  "system.stream-offline.body.profiled": "Lo stream {{profile}} della camera {{deviceName}} è offline",
15849
- "system.node-online.title": "Nodo online — {{nodeId}}",
15850
- "system.node-online.body.agent": "L'agent {{nodeId}} è online",
15851
- "system.node-offline.title": "Nodo offline — {{nodeId}}",
15852
- "system.node-offline.body.agent": "L'agent {{nodeId}} è offline",
15857
+ "system.node-online.title": "Nodo online — {{nodeName}}",
15858
+ "system.node-online.body.agent": "L'agent {{nodeName}} è online",
15859
+ "system.node-online.body.agent.after": "L'agent {{nodeName}} è di nuovo online dopo {{offlineFor}} offline",
15860
+ "system.node-offline.title": "Nodo offline — {{nodeName}}",
15861
+ "system.node-offline.body.agent": "L'agent {{nodeName}} è offline",
15862
+ "system.node-offline.body.agent.connection-lost": "L'agent {{nodeName}} ha perso la connessione con l'hub",
15863
+ "system.node-offline.body.agent.shutdown": "L'agent {{nodeName}} si è spento",
15853
15864
  "system.node-inference-unavailable.title": "Nodo senza inferenza — {{nodeId}}",
15854
15865
  "system.node-inference-unavailable.body": "Il nodo {{nodeId}} non può più eseguire inferenza — tutti i dispositivi abilitati non sono disponibili ({{devices}}). Non riceverà camere finché uno non torna disponibile.",
15855
15866
  "system.addon-crash-loop.title": "Crash-loop addon — {{nodeId}}",
@@ -18478,6 +18489,7 @@ function planRuleTest(input) {
18478
18489
  deviceId: String(systemScope.deviceId ?? deviceId),
18479
18490
  deviceName: `camera ${String(systemScope.deviceId ?? deviceId)}`,
18480
18491
  nodeId: systemScope.nodeId ?? "hub",
18492
+ nodeName: systemScope.nodeId ?? "hub",
18481
18493
  packageName: systemScope.packageName ?? "@camstack/server",
18482
18494
  currentVersion: "1.0.0",
18483
18495
  latestVersion: "1.0.1",
@@ -26972,6 +26984,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
26972
26984
  currentVersion: typeof systemEvent?.data["currentVersion"] === "string" ? systemEvent.data["currentVersion"] : "",
26973
26985
  latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : "",
26974
26986
  ...systemEvent?.deviceId !== void 0 ? { deviceName: String(systemEvent.deviceId) } : {},
26987
+ ...systemEvent?.nodeId !== void 0 && (systemEvent.kind === "node-online" || systemEvent.kind === "node-offline") ? { nodeName: systemEvent.nodeId } : {},
26975
26988
  ...systemEvent?.textParams
26976
26989
  };
26977
26990
  }
@@ -28922,6 +28935,11 @@ var NcJudgedTracks = class {
28922
28935
  //#endregion
28923
28936
  //#region src/notification-center/liveness-holdoff.ts
28924
28937
  var DEVICE_LIVENESS_HOLDOFF_MS = 5 * 6e4;
28938
+ /** Grace before a node offline (or its recovery) is announced — see the docblock. */
28939
+ var NODE_LIVENESS_HOLDOFF_MS = 6e4;
28940
+ function isNodeLivenessKind(kind) {
28941
+ return kind === "node-online" || kind === "node-offline";
28942
+ }
28925
28943
  function isDeviceLivenessKind(kind) {
28926
28944
  return kind === "device-online" || kind === "device-offline" || kind === "camera-online" || kind === "camera-offline";
28927
28945
  }
@@ -28933,6 +28951,10 @@ var DeviceLivenessHoldoff = class {
28933
28951
  pending = /* @__PURE__ */ new Map();
28934
28952
  lastNotified = /* @__PURE__ */ new Map();
28935
28953
  timers = /* @__PURE__ */ new Map();
28954
+ /** First divergence from the last notified state, per subject (cleared on a bounce back). */
28955
+ departedAt = /* @__PURE__ */ new Map();
28956
+ /** When the last notified state began, per subject. */
28957
+ stateSince = /* @__PURE__ */ new Map();
28936
28958
  delayMs;
28937
28959
  schedule;
28938
28960
  cancel;
@@ -28945,6 +28967,11 @@ var DeviceLivenessHoldoff = class {
28945
28967
  /** The seeded (or last delivered) state — a bounce back to this is silence. */
28946
28968
  remember(subject, state) {
28947
28969
  this.lastNotified.set(subject, state);
28970
+ const pending = this.timers.get(subject);
28971
+ if (pending !== void 0) this.cancel(pending);
28972
+ this.timers.delete(subject);
28973
+ this.pending.delete(subject);
28974
+ this.departedAt.delete(subject);
28948
28975
  }
28949
28976
  /** Directory-seeded rows never pass through {@link remember}; first flip does. */
28950
28977
  rememberIfAbsent(subject, state) {
@@ -28953,6 +28980,7 @@ var DeviceLivenessHoldoff = class {
28953
28980
  /** A real ledger flip. Starts / resets the trailing timer. */
28954
28981
  arm(subject, incoming) {
28955
28982
  this.pending.set(subject, incoming);
28983
+ this.trackDeparture(subject, incoming);
28956
28984
  const existing = this.timers.get(subject);
28957
28985
  if (existing !== void 0) this.cancel(existing);
28958
28986
  if (this.delayMs <= 0) {
@@ -28970,6 +28998,16 @@ var DeviceLivenessHoldoff = class {
28970
28998
  for (const id of this.timers.values()) this.cancel(id);
28971
28999
  this.timers.clear();
28972
29000
  this.pending.clear();
29001
+ this.departedAt.clear();
29002
+ }
29003
+ trackDeparture(subject, incoming) {
29004
+ const kind = incoming.subject.systemEvent?.kind;
29005
+ if (kind === void 0) return;
29006
+ if (this.lastNotified.get(subject) === livenessStateOfKind(kind)) {
29007
+ this.departedAt.delete(subject);
29008
+ return;
29009
+ }
29010
+ if (!this.departedAt.has(subject)) this.departedAt.set(subject, incoming.subject.timestamp);
28973
29011
  }
28974
29012
  fire(subject) {
28975
29013
  const incoming = this.pending.get(subject);
@@ -28980,10 +29018,64 @@ var DeviceLivenessHoldoff = class {
28980
29018
  const state = livenessStateOfKind(kind);
28981
29019
  if (this.lastNotified.get(subject) === state) return;
28982
29020
  this.lastNotified.set(subject, state);
28983
- this.flush(incoming);
29021
+ const since = this.departedAt.get(subject) ?? incoming.subject.timestamp;
29022
+ const previousSince = this.stateSince.get(subject);
29023
+ this.departedAt.delete(subject);
29024
+ this.stateSince.set(subject, since);
29025
+ this.flush(incoming, previousSince !== void 0 ? {
29026
+ since,
29027
+ previousSince
29028
+ } : { since });
28984
29029
  }
28985
29030
  };
28986
29031
  //#endregion
29032
+ //#region src/notification-center/node-liveness-text.ts
29033
+ /** "45 s", "12 min", "2 h 5 min", "3 d 4 h" — unit symbols read the same in en and it. */
29034
+ function formatOutageDuration(ms) {
29035
+ const totalSec = Math.max(0, Math.round(ms / 1e3));
29036
+ if (totalSec < 60) return `${totalSec} s`;
29037
+ const totalMin = Math.floor(totalSec / 60);
29038
+ if (totalMin < 60) return `${totalMin} min`;
29039
+ const totalHours = Math.floor(totalMin / 60);
29040
+ const minutes = totalMin % 60;
29041
+ if (totalHours < 24) return minutes > 0 ? `${totalHours} h ${minutes} min` : `${totalHours} h`;
29042
+ const days = Math.floor(totalHours / 24);
29043
+ const hours = totalHours % 24;
29044
+ return hours > 0 ? `${days} d ${hours} h` : `${days} d`;
29045
+ }
29046
+ /**
29047
+ * Freeze the post-hold-off facts onto a node liveness event.
29048
+ *
29049
+ * - `nodeName`: the display name, when the directory knew one.
29050
+ * - `offlineFor` (recovery only, and only when the outage START is known — a
29051
+ * state that was merely seeded after a restart has none): the outage length,
29052
+ * with the `.after` body variant that says it.
29053
+ */
29054
+ function withNodeLivenessFacts(incoming, facts) {
29055
+ const system = incoming.subject.systemEvent;
29056
+ if (system === void 0) return incoming;
29057
+ const { since, previousSince } = facts.timing;
29058
+ const recovered = system.kind === "node-online" && previousSince !== void 0;
29059
+ const name = facts.nodeName !== void 0 && facts.nodeName.length > 0 ? facts.nodeName : void 0;
29060
+ if (!recovered && name === void 0) return incoming;
29061
+ const textParams = {
29062
+ ...system.textParams,
29063
+ ...name !== void 0 ? { nodeName: name } : {},
29064
+ ...recovered ? { offlineFor: formatOutageDuration(since - previousSince) } : {}
29065
+ };
29066
+ return {
29067
+ ...incoming,
29068
+ subject: {
29069
+ ...incoming.subject,
29070
+ systemEvent: {
29071
+ ...system,
29072
+ ...recovered ? { bodyKey: "system.node-online.body.agent.after" } : {},
29073
+ textParams
29074
+ }
29075
+ }
29076
+ };
29077
+ }
29078
+ //#endregion
28987
29079
  //#region src/notification-center/linked-camera-media.ts
28988
29080
  /** ADR 0632: absent = mosaic, applied HERE and never as a Zod default. */
28989
29081
  function linkedCameraMode(media) {
@@ -38303,6 +38395,8 @@ var ALARM_PANEL_CAP = require_clip_model_registry.alarmPanelCapability.name;
38303
38395
  /** Bound on remembered departure fallbacks (D691 review M3) — the shelf's own
38304
38396
  * capture bound. */
38305
38397
  var NC_DEPARTURE_FALLBACK_MAX = 64;
38398
+ /** Bound on the post-hold-off `nodes.directory` read that names a node. */
38399
+ var NODE_NAME_READ_TIMEOUT_MS = 3e3;
38306
38400
  /** Bound on the sampled "departed track alias resolution failed" memo
38307
38401
  * (D683 × D691) — the alias-resolution call sites' own shape. */
38308
38402
  var NC_DEPARTED_ALIAS_FAILURE_MEMO_MAX = 256;
@@ -39099,6 +39193,8 @@ var NotificationCenter = class {
39099
39193
  addonUpdates;
39100
39194
  /** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
39101
39195
  livenessHoldoff;
39196
+ /** Trailing 60 s hold-off for node liveness (reconnect storms). */
39197
+ nodeLivenessHoldoff;
39102
39198
  /**
39103
39199
  * Serialized evaluation chain. The persist hooks are fire-and-forget for
39104
39200
  * the frame path, but two concurrent evaluations of the same device
@@ -39800,6 +39896,7 @@ var NotificationCenter = class {
39800
39896
  if (incoming !== null) this.consumeEvent(incoming);
39801
39897
  });
39802
39898
  this.livenessHoldoff = new DeviceLivenessHoldoff((event) => this.consumeEvent(event), { delayMs: deps.livenessHoldoffMs ?? (process.env.VITEST ? 0 : DEVICE_LIVENESS_HOLDOFF_MS) });
39899
+ this.nodeLivenessHoldoff = new DeviceLivenessHoldoff((event, timing) => this.consumeNodeLiveness(event, timing), { delayMs: deps.nodeLivenessHoldoffMs ?? (process.env.VITEST ? 0 : NODE_LIVENESS_HOLDOFF_MS) });
39803
39900
  this.actionRunner = deps.actuate !== void 0 && deps.isCapDeviceScoped !== void 0 ? new NcRuleActionRunner({
39804
39901
  logger: this.logger.child("actions"),
39805
39902
  actuate: (input) => this.actuateWithAttribution(input),
@@ -40361,6 +40458,7 @@ var NotificationCenter = class {
40361
40458
  this.linkedStills?.clear();
40362
40459
  this.addonUpdates.dispose();
40363
40460
  this.livenessHoldoff.dispose();
40461
+ this.nodeLivenessHoldoff.dispose();
40364
40462
  this.evaluationActive = false;
40365
40463
  this.hydration = null;
40366
40464
  }
@@ -40433,6 +40531,42 @@ var NotificationCenter = class {
40433
40531
  this.scheduleEvaluation(event);
40434
40532
  }
40435
40533
  /**
40534
+ * A node liveness flip that survived its hold-off: freeze the node's display
40535
+ * name and (on a recovery) the outage length onto it, then consume it.
40536
+ *
40537
+ * The name read is bounded and best-effort — a slow or failed directory
40538
+ * costs the friendly name, never the notification (the intake already froze
40539
+ * the id as `nodeName`).
40540
+ */
40541
+ consumeNodeLiveness(event, timing) {
40542
+ const nodeId = event.subject.systemEvent?.nodeId;
40543
+ this.readNodeDisplayName(nodeId).then((nodeName) => {
40544
+ this.consumeEvent(withNodeLivenessFacts(event, nodeName !== void 0 ? {
40545
+ timing,
40546
+ nodeName
40547
+ } : { timing }));
40548
+ });
40549
+ }
40550
+ async readNodeDisplayName(nodeId) {
40551
+ const read = this.deps.readNodeDirectory;
40552
+ if (nodeId === void 0 || read === void 0) return void 0;
40553
+ let timer;
40554
+ try {
40555
+ const timeout = new Promise((resolve) => {
40556
+ timer = setTimeout(() => resolve(void 0), NODE_NAME_READ_TIMEOUT_MS);
40557
+ });
40558
+ return (await Promise.race([read(), timeout]))?.nodes.find((node) => node.id === nodeId)?.name;
40559
+ } catch (err) {
40560
+ this.logger.debug("node display name unavailable — notifying with the id", { meta: {
40561
+ nodeId,
40562
+ error: String(err)
40563
+ } });
40564
+ return;
40565
+ } finally {
40566
+ if (timer !== void 0) clearTimeout(timer);
40567
+ }
40568
+ }
40569
+ /**
40436
40570
  * The same consumption, awaited, with the verdict returned. Identical code
40437
40571
  * path — {@link consumeEvent} is this function with the report discarded — so
40438
40572
  * a producer that reads the report is testing what the live producers do, not
@@ -41365,8 +41499,9 @@ var NotificationCenter = class {
41365
41499
  if (observation !== null) {
41366
41500
  const previous = this.liveness.stateOf(observation.subject);
41367
41501
  const verdict = this.liveness.observe(observation, this.now());
41502
+ const holdoff = isNodeLivenessKind(observation.kind) ? this.nodeLivenessHoldoff : this.livenessHoldoff;
41368
41503
  if (verdict === "seeded") {
41369
- this.livenessHoldoff.remember(observation.subject, observation.state);
41504
+ holdoff.remember(observation.subject, observation.state);
41370
41505
  this.reportLivenessDrop(incoming, verdict);
41371
41506
  return;
41372
41507
  }
@@ -41374,9 +41509,9 @@ var NotificationCenter = class {
41374
41509
  this.reportLivenessDrop(incoming, verdict);
41375
41510
  return;
41376
41511
  }
41377
- if (previous !== void 0) this.livenessHoldoff.rememberIfAbsent(observation.subject, previous);
41512
+ if (previous !== void 0) holdoff.rememberIfAbsent(observation.subject, previous);
41378
41513
  if (observation.state === "online" && !this.deviceAlertSettings().notifyRecovery) {
41379
- this.livenessHoldoff.remember(observation.subject, observation.state);
41514
+ holdoff.remember(observation.subject, observation.state);
41380
41515
  this.logger.debug("device came back — the all-clear is switched off", {
41381
41516
  tags: incoming.log().tags,
41382
41517
  meta: {
@@ -41386,8 +41521,8 @@ var NotificationCenter = class {
41386
41521
  });
41387
41522
  return;
41388
41523
  }
41389
- if (isDeviceLivenessKind(observation.kind)) {
41390
- this.livenessHoldoff.arm(observation.subject, incoming);
41524
+ if (isDeviceLivenessKind(observation.kind) || isNodeLivenessKind(observation.kind)) {
41525
+ holdoff.arm(observation.subject, incoming);
41391
41526
  return;
41392
41527
  }
41393
41528
  } else if (system !== void 0 && (system.kind.startsWith("device-") || system.kind.startsWith("stream-"))) {
@@ -83507,6 +83642,260 @@ function withDatasetVersion(manifest, version) {
83507
83642
  };
83508
83643
  }
83509
83644
  //#endregion
83645
+ //#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
83646
+ /**
83647
+ * @durable class=ledger owner=pipeline-analytics
83648
+ * write="one row per dataset version the operator creates: a frozen frameIds list,
83649
+ * the sha256 of the annotations those frames carried, the export parameters and
83650
+ * the counts at creation. Never updated after the insert — a different selection
83651
+ * is a new version."
83652
+ * retention="none — rows leave only when the operator deletes the version. The
83653
+ * frames themselves are owned by the retrain corpus; deleting a version deletes no
83654
+ * frame, and deleting a frame does not edit a version (verify reports it missing)."
83655
+ */
83656
+ var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
83657
+ var COLUMNS = [
83658
+ {
83659
+ name: "id",
83660
+ type: "TEXT",
83661
+ primaryKey: true,
83662
+ notNull: true
83663
+ },
83664
+ {
83665
+ name: "name",
83666
+ type: "TEXT",
83667
+ notNull: true
83668
+ },
83669
+ {
83670
+ name: "nameKey",
83671
+ type: "TEXT",
83672
+ notNull: true
83673
+ },
83674
+ {
83675
+ name: "createdAt",
83676
+ type: "INTEGER",
83677
+ notNull: true
83678
+ },
83679
+ {
83680
+ name: "selection",
83681
+ type: "JSON",
83682
+ notNull: true
83683
+ },
83684
+ {
83685
+ name: "layout",
83686
+ type: "TEXT",
83687
+ notNull: true
83688
+ },
83689
+ {
83690
+ name: "completeOnly",
83691
+ type: "BOOLEAN",
83692
+ notNull: true
83693
+ },
83694
+ {
83695
+ name: "frameIds",
83696
+ type: "JSON",
83697
+ notNull: true
83698
+ },
83699
+ {
83700
+ name: "annotationHash",
83701
+ type: "TEXT",
83702
+ notNull: true
83703
+ },
83704
+ {
83705
+ name: "manifestSchema",
83706
+ type: "TEXT",
83707
+ notNull: true
83708
+ },
83709
+ {
83710
+ name: "stats",
83711
+ type: "JSON",
83712
+ notNull: true
83713
+ }
83714
+ ];
83715
+ var INDEXES = [{
83716
+ name: "idx_retrain_dataset_versions_name_key",
83717
+ columns: ["nameKey"],
83718
+ unique: true
83719
+ }, {
83720
+ name: "idx_retrain_dataset_versions_created",
83721
+ columns: ["createdAt"]
83722
+ }];
83723
+ function summaryOfVersion(version) {
83724
+ const { frameIds, ...rest } = version;
83725
+ return {
83726
+ ...rest,
83727
+ frameCount: frameIds.length
83728
+ };
83729
+ }
83730
+ var nameKeyOf = (name) => name.trim().toLowerCase();
83731
+ function numberList(value) {
83732
+ return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
83733
+ }
83734
+ function stringList(value) {
83735
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
83736
+ }
83737
+ function parseJson(value) {
83738
+ if (typeof value !== "string") return value;
83739
+ try {
83740
+ return JSON.parse(value);
83741
+ } catch {
83742
+ return;
83743
+ }
83744
+ }
83745
+ function asRecord(value) {
83746
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
83747
+ }
83748
+ var EMPTY_STATS = {
83749
+ frames: 0,
83750
+ completeFrames: 0,
83751
+ completeness: 0,
83752
+ subjects: 0,
83753
+ modelErrors: 0,
83754
+ hardNegatives: 0,
83755
+ cells: [],
83756
+ cameras: []
83757
+ };
83758
+ /** A stored stats object, trusted only as far as its shape. */
83759
+ function readStats(value) {
83760
+ const raw = asRecord(parseJson(value));
83761
+ const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
83762
+ const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
83763
+ const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
83764
+ return {
83765
+ ...EMPTY_STATS,
83766
+ frames: num("frames"),
83767
+ completeFrames: num("completeFrames"),
83768
+ completeness: num("completeness"),
83769
+ subjects: num("subjects"),
83770
+ modelErrors: num("modelErrors"),
83771
+ hardNegatives: num("hardNegatives"),
83772
+ cells: cells.map((c) => {
83773
+ const r = asRecord(c);
83774
+ const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
83775
+ return {
83776
+ macroClass: String(r["macroClass"] ?? ""),
83777
+ deviceId: Number(r["deviceId"] ?? 0),
83778
+ lighting,
83779
+ subjects: Number(r["subjects"] ?? 0),
83780
+ frames: Number(r["frames"] ?? 0)
83781
+ };
83782
+ }),
83783
+ cameras: cameras.map((c) => {
83784
+ const r = asRecord(c);
83785
+ return {
83786
+ deviceId: Number(r["deviceId"] ?? 0),
83787
+ frames: Number(r["frames"] ?? 0),
83788
+ completeFrames: Number(r["completeFrames"] ?? 0)
83789
+ };
83790
+ })
83791
+ };
83792
+ }
83793
+ var RetrainDatasetVersionStore = class {
83794
+ store;
83795
+ logger;
83796
+ constructor(deps) {
83797
+ this.store = deps.store;
83798
+ this.logger = deps.logger;
83799
+ }
83800
+ static async declare(store) {
83801
+ await store.declareCollection.mutate({
83802
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83803
+ columns: [...COLUMNS],
83804
+ indexes: [...INDEXES]
83805
+ });
83806
+ }
83807
+ insert = async (version) => {
83808
+ await this.store.set.mutate({
83809
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83810
+ key: version.id,
83811
+ value: {
83812
+ name: version.name,
83813
+ nameKey: nameKeyOf(version.name),
83814
+ createdAt: version.createdAt,
83815
+ selection: version.selection,
83816
+ layout: version.layout,
83817
+ completeOnly: version.completeOnly,
83818
+ frameIds: version.frameIds,
83819
+ annotationHash: version.annotationHash,
83820
+ manifestSchema: version.manifestSchema,
83821
+ stats: version.stats
83822
+ }
83823
+ });
83824
+ };
83825
+ get = async (id) => {
83826
+ try {
83827
+ const row = await this.store.get.query({
83828
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83829
+ key: id
83830
+ });
83831
+ return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
83832
+ } catch (err) {
83833
+ this.logger.warn("retrain dataset version read failed", { meta: {
83834
+ versionId: id,
83835
+ error: String(err)
83836
+ } });
83837
+ return null;
83838
+ }
83839
+ };
83840
+ list = async () => {
83841
+ return (await this.listRows()).map(summaryOfVersion);
83842
+ };
83843
+ /** Every row WITH its frame list — the migration into `training` reads this. */
83844
+ listRows = async () => {
83845
+ try {
83846
+ return (await this.store.query.query({
83847
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83848
+ filter: { orderBy: {
83849
+ field: "createdAt",
83850
+ direction: "desc"
83851
+ } }
83852
+ })).map((r) => this.toVersion(r.id, asRecord(r.data)));
83853
+ } catch (err) {
83854
+ this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
83855
+ return [];
83856
+ }
83857
+ };
83858
+ findIdByName = async (name) => {
83859
+ return (await this.store.query.query({
83860
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83861
+ filter: {
83862
+ where: { nameKey: nameKeyOf(name) },
83863
+ limit: 1
83864
+ }
83865
+ }))[0]?.id ?? null;
83866
+ };
83867
+ remove = async (id) => {
83868
+ if (await this.get(id) === null) return false;
83869
+ await this.store.delete.mutate({
83870
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83871
+ key: id
83872
+ });
83873
+ return true;
83874
+ };
83875
+ toVersion(id, data) {
83876
+ const selection = asRecord(parseJson(data["selection"]));
83877
+ const deviceIds = numberList(selection["deviceIds"]);
83878
+ const collectionIds = stringList(selection["collectionIds"]);
83879
+ const frameIdsSel = stringList(selection["frameIds"]);
83880
+ return {
83881
+ id,
83882
+ name: String(data["name"] ?? ""),
83883
+ createdAt: Number(data["createdAt"] ?? 0),
83884
+ selection: {
83885
+ ...deviceIds !== void 0 ? { deviceIds } : {},
83886
+ ...collectionIds !== void 0 ? { collectionIds } : {},
83887
+ ...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
83888
+ },
83889
+ layout: data["layout"] === "crops" ? "crops" : "frames",
83890
+ completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
83891
+ frameIds: stringList(parseJson(data["frameIds"])) ?? [],
83892
+ annotationHash: String(data["annotationHash"] ?? ""),
83893
+ manifestSchema: String(data["manifestSchema"] ?? ""),
83894
+ stats: readStats(data["stats"])
83895
+ };
83896
+ }
83897
+ };
83898
+ //#endregion
83510
83899
  //#region src/pipeline-analytics/retrain/retrain-geometry.ts
83511
83900
  function clamp01(v) {
83512
83901
  return v < 0 ? 0 : v > 1 ? 1 : v;
@@ -83795,13 +84184,6 @@ function filterOf(selection, completeOnly) {
83795
84184
  ...completeOnly ? { completeOnly: true } : {}
83796
84185
  };
83797
84186
  }
83798
- function summaryOf(version) {
83799
- const { frameIds, ...rest } = version;
83800
- return {
83801
- ...rest,
83802
- frameCount: frameIds.length
83803
- };
83804
- }
83805
84187
  var RetrainDatasetService = class {
83806
84188
  deps;
83807
84189
  constructor(deps) {
@@ -83854,11 +84236,11 @@ var RetrainDatasetService = class {
83854
84236
  } });
83855
84237
  return {
83856
84238
  kind: "created",
83857
- version: summaryOf(version)
84239
+ version: summaryOfVersion(version)
83858
84240
  };
83859
84241
  }
83860
84242
  async list() {
83861
- return (await this.deps.versions.list()).map(summaryOf);
84243
+ return this.deps.versions.list();
83862
84244
  }
83863
84245
  async delete(input) {
83864
84246
  const deleted = await this.deps.versions.remove(input.id);
@@ -83916,246 +84298,62 @@ var RetrainDatasetService = class {
83916
84298
  }
83917
84299
  };
83918
84300
  //#endregion
83919
- //#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
83920
- /**
83921
- * @durable class=ledger owner=pipeline-analytics
83922
- * write="one row per dataset version the operator creates: a frozen frameIds list,
83923
- * the sha256 of the annotations those frames carried, the export parameters and
83924
- * the counts at creation. Never updated after the insert — a different selection
83925
- * is a new version."
83926
- * retention="none — rows leave only when the operator deletes the version. The
83927
- * frames themselves are owned by the retrain corpus; deleting a version deletes no
83928
- * frame, and deleting a frame does not edit a version (verify reports it missing)."
83929
- */
83930
- var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
83931
- var COLUMNS = [
83932
- {
83933
- name: "id",
83934
- type: "TEXT",
83935
- primaryKey: true,
83936
- notNull: true
83937
- },
83938
- {
83939
- name: "name",
83940
- type: "TEXT",
83941
- notNull: true
83942
- },
83943
- {
83944
- name: "nameKey",
83945
- type: "TEXT",
83946
- notNull: true
83947
- },
83948
- {
83949
- name: "createdAt",
83950
- type: "INTEGER",
83951
- notNull: true
83952
- },
83953
- {
83954
- name: "selection",
83955
- type: "JSON",
83956
- notNull: true
83957
- },
83958
- {
83959
- name: "layout",
83960
- type: "TEXT",
83961
- notNull: true
83962
- },
83963
- {
83964
- name: "completeOnly",
83965
- type: "BOOLEAN",
83966
- notNull: true
83967
- },
83968
- {
83969
- name: "frameIds",
83970
- type: "JSON",
83971
- notNull: true
83972
- },
83973
- {
83974
- name: "annotationHash",
83975
- type: "TEXT",
83976
- notNull: true
83977
- },
83978
- {
83979
- name: "manifestSchema",
83980
- type: "TEXT",
83981
- notNull: true
83982
- },
83983
- {
83984
- name: "stats",
83985
- type: "JSON",
83986
- notNull: true
83987
- }
83988
- ];
83989
- var INDEXES = [{
83990
- name: "idx_retrain_dataset_versions_name_key",
83991
- columns: ["nameKey"],
83992
- unique: true
83993
- }, {
83994
- name: "idx_retrain_dataset_versions_created",
83995
- columns: ["createdAt"]
83996
- }];
83997
- var nameKeyOf = (name) => name.trim().toLowerCase();
83998
- function numberList(value) {
83999
- return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
84000
- }
84001
- function stringList(value) {
84002
- return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
84003
- }
84004
- function parseJson(value) {
84005
- if (typeof value !== "string") return value;
84006
- try {
84007
- return JSON.parse(value);
84008
- } catch {
84009
- return;
84010
- }
84011
- }
84012
- function asRecord(value) {
84013
- return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
84014
- }
84015
- var EMPTY_STATS = {
84016
- frames: 0,
84017
- completeFrames: 0,
84018
- completeness: 0,
84019
- subjects: 0,
84020
- modelErrors: 0,
84021
- hardNegatives: 0,
84022
- cells: [],
84023
- cameras: []
84024
- };
84025
- /** A stored stats object, trusted only as far as its shape. */
84026
- function readStats(value) {
84027
- const raw = asRecord(parseJson(value));
84028
- const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
84029
- const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
84030
- const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
84031
- return {
84032
- ...EMPTY_STATS,
84033
- frames: num("frames"),
84034
- completeFrames: num("completeFrames"),
84035
- completeness: num("completeness"),
84036
- subjects: num("subjects"),
84037
- modelErrors: num("modelErrors"),
84038
- hardNegatives: num("hardNegatives"),
84039
- cells: cells.map((c) => {
84040
- const r = asRecord(c);
84041
- const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
84042
- return {
84043
- macroClass: String(r["macroClass"] ?? ""),
84044
- deviceId: Number(r["deviceId"] ?? 0),
84045
- lighting,
84046
- subjects: Number(r["subjects"] ?? 0),
84047
- frames: Number(r["frames"] ?? 0)
84048
- };
84049
- }),
84050
- cameras: cameras.map((c) => {
84051
- const r = asRecord(c);
84052
- return {
84053
- deviceId: Number(r["deviceId"] ?? 0),
84054
- frames: Number(r["frames"] ?? 0),
84055
- completeFrames: Number(r["completeFrames"] ?? 0)
84056
- };
84057
- })
84058
- };
84059
- }
84060
- var RetrainDatasetVersionStore = class {
84061
- store;
84062
- logger;
84301
+ //#region src/pipeline-analytics/retrain/retrain-dataset-version-routing.ts
84302
+ var RoutingDatasetVersionRepository = class {
84303
+ deps;
84304
+ lastPresence = false;
84305
+ migrated = false;
84306
+ migration = null;
84063
84307
  constructor(deps) {
84064
- this.store = deps.store;
84065
- this.logger = deps.logger;
84066
- }
84067
- static async declare(store) {
84068
- await store.declareCollection.mutate({
84069
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84070
- columns: [...COLUMNS],
84071
- indexes: [...INDEXES]
84072
- });
84308
+ this.deps = deps;
84073
84309
  }
84074
84310
  insert = async (version) => {
84075
- await this.store.set.mutate({
84076
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84077
- key: version.id,
84078
- value: {
84079
- name: version.name,
84080
- nameKey: nameKeyOf(version.name),
84081
- createdAt: version.createdAt,
84082
- selection: version.selection,
84083
- layout: version.layout,
84084
- completeOnly: version.completeOnly,
84085
- frameIds: version.frameIds,
84086
- annotationHash: version.annotationHash,
84087
- manifestSchema: version.manifestSchema,
84088
- stats: version.stats
84089
- }
84090
- });
84311
+ if (await this.route() === "local") {
84312
+ await this.deps.local.insert(version);
84313
+ return;
84314
+ }
84315
+ await this.deps.training.put(version);
84091
84316
  };
84092
- get = async (id) => {
84317
+ get = async (id) => await this.route() === "local" ? this.deps.local.get(id) : this.deps.training.get(id);
84318
+ list = async () => await this.route() === "local" ? this.deps.local.list() : this.deps.training.list();
84319
+ findIdByName = async (name) => await this.route() === "local" ? this.deps.local.findIdByName(name) : this.deps.training.findIdByName(name);
84320
+ remove = async (id) => await this.route() === "local" ? this.deps.local.remove(id) : this.deps.training.remove(id);
84321
+ async route() {
84322
+ if (!await this.presence()) return "local";
84323
+ if (this.migrated) return "training";
84324
+ return await this.migrateOnce() ? "training" : "local";
84325
+ }
84326
+ async presence() {
84327
+ let answer;
84093
84328
  try {
84094
- const row = await this.store.get.query({
84095
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84096
- key: id
84097
- });
84098
- return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
84099
- } catch (err) {
84100
- this.logger.warn("retrain dataset version read failed", { meta: {
84101
- versionId: id,
84102
- error: String(err)
84103
- } });
84104
- return null;
84329
+ answer = await this.deps.trainingPresent();
84330
+ } catch {
84331
+ answer = null;
84105
84332
  }
84106
- };
84107
- list = async () => {
84333
+ if (answer !== null) this.lastPresence = answer;
84334
+ return this.lastPresence;
84335
+ }
84336
+ migrateOnce() {
84337
+ if (this.migration === null) this.migration = this.migrate().finally(() => {
84338
+ this.migration = null;
84339
+ });
84340
+ return this.migration;
84341
+ }
84342
+ async migrate() {
84343
+ const { local, training, logger } = this.deps;
84108
84344
  try {
84109
- return (await this.store.query.query({
84110
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84111
- filter: { orderBy: {
84112
- field: "createdAt",
84113
- direction: "desc"
84114
- } }
84115
- })).map((r) => this.toVersion(r.id, asRecord(r.data)));
84345
+ const rows = await local.listRows();
84346
+ for (const row of rows) {
84347
+ await training.put(row);
84348
+ await local.remove(row.id);
84349
+ }
84350
+ this.migrated = true;
84351
+ if (rows.length > 0) logger.info("retrain dataset versions moved to the training capability", { meta: { versions: rows.length } });
84352
+ return true;
84116
84353
  } catch (err) {
84117
- this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
84118
- return [];
84354
+ logger.warn("retrain dataset versions could not move to training yet — kept local", { meta: { error: err instanceof Error ? err.message : String(err) } });
84355
+ return false;
84119
84356
  }
84120
- };
84121
- findIdByName = async (name) => {
84122
- return (await this.store.query.query({
84123
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84124
- filter: {
84125
- where: { nameKey: nameKeyOf(name) },
84126
- limit: 1
84127
- }
84128
- }))[0]?.id ?? null;
84129
- };
84130
- remove = async (id) => {
84131
- if (await this.get(id) === null) return false;
84132
- await this.store.delete.mutate({
84133
- collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84134
- key: id
84135
- });
84136
- return true;
84137
- };
84138
- toVersion(id, data) {
84139
- const selection = asRecord(parseJson(data["selection"]));
84140
- const deviceIds = numberList(selection["deviceIds"]);
84141
- const collectionIds = stringList(selection["collectionIds"]);
84142
- const frameIdsSel = stringList(selection["frameIds"]);
84143
- return {
84144
- id,
84145
- name: String(data["name"] ?? ""),
84146
- createdAt: Number(data["createdAt"] ?? 0),
84147
- selection: {
84148
- ...deviceIds !== void 0 ? { deviceIds } : {},
84149
- ...collectionIds !== void 0 ? { collectionIds } : {},
84150
- ...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
84151
- },
84152
- layout: data["layout"] === "crops" ? "crops" : "frames",
84153
- completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
84154
- frameIds: stringList(parseJson(data["frameIds"])) ?? [],
84155
- annotationHash: String(data["annotationHash"] ?? ""),
84156
- manifestSchema: String(data["manifestSchema"] ?? ""),
84157
- stats: readStats(data["stats"])
84158
- };
84159
84357
  }
84160
84358
  };
84161
84359
  //#endregion
@@ -84825,6 +85023,61 @@ async function writeRetrainArchiveTo(deps) {
84825
85023
  return stats;
84826
85024
  }
84827
85025
  //#endregion
85026
+ //#region src/pipeline-analytics/retrain/retrain-archive-offer.ts
85027
+ /** A reader silent this long is gone; the body is cut. */
85028
+ var RETRAIN_OFFER_STALL_MS = 6e4;
85029
+ function responseArchiveSink(res, stallMs = RETRAIN_OFFER_STALL_MS) {
85030
+ let closed = false;
85031
+ res.once("close", () => {
85032
+ closed = true;
85033
+ });
85034
+ return { write: (chunk) => new Promise((resolve, reject) => {
85035
+ if (closed || res.destroyed) {
85036
+ reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
85037
+ return;
85038
+ }
85039
+ if (res.write(chunk)) {
85040
+ resolve();
85041
+ return;
85042
+ }
85043
+ const onDrain = () => {
85044
+ cleanup();
85045
+ resolve();
85046
+ };
85047
+ const onClose = () => {
85048
+ cleanup();
85049
+ reject(/* @__PURE__ */ new Error("retrain archive reader went away"));
85050
+ };
85051
+ const timer = setTimeout(() => {
85052
+ cleanup();
85053
+ res.destroy(/* @__PURE__ */ new Error("retrain archive reader stalled"));
85054
+ reject(/* @__PURE__ */ new Error(`retrain archive reader stalled for ${stallMs} ms`));
85055
+ }, stallMs);
85056
+ const cleanup = () => {
85057
+ clearTimeout(timer);
85058
+ res.off("drain", onDrain);
85059
+ res.off("close", onClose);
85060
+ };
85061
+ res.once("drain", onDrain);
85062
+ res.once("close", onClose);
85063
+ }) };
85064
+ }
85065
+ /**
85066
+ * Serve one archive: headers, the writer's chunks, the end. A writer that
85067
+ * throws destroys the response (the reader sees a truncated body, never a
85068
+ * clean end) and the error propagates to the caller's log.
85069
+ */
85070
+ async function serveRetrainArchive(_req, res, writeArchive) {
85071
+ res.writeHead(200, { "content-type": "application/x-tar" });
85072
+ try {
85073
+ await writeArchive(responseArchiveSink(res));
85074
+ await new Promise((resolve) => res.end(resolve));
85075
+ } catch (err) {
85076
+ res.destroy(err instanceof Error ? err : new Error(String(err)));
85077
+ throw err;
85078
+ }
85079
+ }
85080
+ //#endregion
84828
85081
  //#region src/pipeline-analytics/retrain/retrain-crop-cut.ts
84829
85082
  /**
84830
85083
  * The `crops` layout's cutter: one subject crop from a retrain frame copy.
@@ -111121,8 +111374,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
111121
111374
  logger: logger.child("RetrainDatasets")
111122
111375
  });
111123
111376
  this.retrainDatasetVersions = retrainDatasetVersions;
111377
+ const routedDatasetVersions = new RoutingDatasetVersionRepository({
111378
+ local: retrainDatasetVersions,
111379
+ training: {
111380
+ put: (row) => api.training.putDatasetVersion.mutate({ ...row }),
111381
+ get: (id) => api.training.getDatasetVersion.query({ id }),
111382
+ list: async () => api.training.listDatasetVersions.query({}),
111383
+ findIdByName: async (name) => (await api.training.findDatasetVersionIdByName.query({ name })).id,
111384
+ remove: async (id) => (await api.training.removeDatasetVersion.mutate({ id })).removed
111385
+ },
111386
+ trainingPresent: async () => {
111387
+ try {
111388
+ return (await api.addons.listCapabilityProviders.query({ capName: "training" })).length > 0;
111389
+ } catch {
111390
+ return null;
111391
+ }
111392
+ },
111393
+ logger: logger.child("RetrainDatasets")
111394
+ });
111124
111395
  this.retrainDatasets = new RetrainDatasetService({
111125
- versions: retrainDatasetVersions,
111396
+ versions: routedDatasetVersions,
111126
111397
  buildPlan: (request) => this.buildRetrainExportPlan(request),
111127
111398
  now: () => Date.now(),
111128
111399
  newId: () => (0, node_crypto.randomUUID)(),
@@ -112487,6 +112758,51 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
112487
112758
  }
112488
112759
  }
112489
112760
  /**
112761
+ * The same archive behind a one-shot `ctx.peerBytes` ticket (D805): the
112762
+ * caller (a training provider, same host) dials it at once and streams it
112763
+ * into its cloud. The plan is built now, so an empty or unknown selection
112764
+ * fails here rather than as an empty body; the bytes and their sha256 are
112765
+ * the reader's to measure.
112766
+ */
112767
+ async offerRetrainArchive(input) {
112768
+ const frameStore = this.retrainFrames;
112769
+ if (frameStore === null) throw new Error("retrain export: analytics stores unavailable");
112770
+ const peerBytes = this.ctx.peerBytes;
112771
+ if (peerBytes === void 0) throw new Error("retrain archive offer: no peer-bytes facility");
112772
+ const plan = await this.buildRetrainExportPlan({
112773
+ filter: input.filter,
112774
+ layout: input.layout
112775
+ });
112776
+ if (plan.frames.length === 0) throw new Error("retrain archive offer: the selection holds no frame");
112777
+ const logger = this.ctx.logger;
112778
+ const offered = await peerBytes.offer({
112779
+ label: "retrain-archive",
112780
+ handler: (req, res) => serveRetrainArchive(req, res, async (sink) => {
112781
+ await writeRetrainArchiveTo({
112782
+ plan,
112783
+ readFrame: (frame) => frameStore.readFrame(frame),
112784
+ cutCrop: this.retrainCropCutter(),
112785
+ sink,
112786
+ markExported: (frameIds, batchId) => this.markRetrainExported(frameIds, batchId),
112787
+ logger,
112788
+ now: () => Date.now()
112789
+ });
112790
+ }).catch((err) => {
112791
+ logger.warn("retrain archive offer failed mid-body", { meta: { error: require_clip_model_registry.errMsg(err) } });
112792
+ })
112793
+ });
112794
+ if (offered.kind === "refused") throw new Error(`retrain archive offer refused: ${offered.code} — ${offered.detail}`);
112795
+ logger.info("retrain archive offered", { meta: {
112796
+ frames: plan.frames.length,
112797
+ layout: input.layout
112798
+ } });
112799
+ return {
112800
+ ticket: offered.ticket,
112801
+ frames: plan.frames.length,
112802
+ annotations: plan.manifest.annotationCount
112803
+ };
112804
+ }
112805
+ /**
112490
112806
  * The store seam the `debug.*` actions and the debug bundle both read through.
112491
112807
  *
112492
112808
  * Index reads, plus ONE existence probe per media row — never a blob. The