@camstack/addon-pipeline 1.2.309 → 1.2.310

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.
@@ -5298,6 +5298,19 @@ async function resolveMotionFrameView(registry, ref) {
5298
5298
  /** The step id (`PipelineStepInput.addonId`) the governor gates. */
5299
5299
  var PACKAGE_DETECTION_STEP_ID = "package-detection";
5300
5300
  /**
5301
+ * The executor's frame, stamped `packageLooked` when the `package-detection`
5302
+ * root was among the steps actually DISPATCHED for it (the governor let it
5303
+ * through) and the frame was not shed. Post-analysis reads it as "an empty
5304
+ * doormat on this frame is an ABSENCE, not silence" (D687). Never mutates.
5305
+ */
5306
+ function withPackageLook(result, dispatched) {
5307
+ if (result.shed === true) return result;
5308
+ return dispatched.some((s) => s.addonId === "package-detection" && s.enabled !== false) ? {
5309
+ ...result,
5310
+ packageLooked: true
5311
+ } : result;
5312
+ }
5313
+ /**
5301
5314
  * Burst: 5 runs, 5s apart — 0s, 5s, 10s, 15s, 20s after the settle edge.
5302
5315
  *
5303
5316
  * Sized as a SAFETY NET when the operator raises `packageDropDwellSec` above
@@ -11385,7 +11398,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
11385
11398
  return null;
11386
11399
  }
11387
11400
  if (result === null) return null;
11388
- return this.recoverSmallSubjects(deviceId, frame, result, dispatchSteps, camConfig?.deviceKey);
11401
+ const looked = withPackageLook(result, dispatchSteps);
11402
+ return this.recoverSmallSubjects(deviceId, frame, looked, dispatchSteps, camConfig?.deviceKey);
11389
11403
  }
11390
11404
  /**
11391
11405
  * The second detector pass at native resolution — `small-subject-recovery.ts`
@@ -5290,6 +5290,19 @@ async function resolveMotionFrameView(registry, ref) {
5290
5290
  /** The step id (`PipelineStepInput.addonId`) the governor gates. */
5291
5291
  var PACKAGE_DETECTION_STEP_ID = "package-detection";
5292
5292
  /**
5293
+ * The executor's frame, stamped `packageLooked` when the `package-detection`
5294
+ * root was among the steps actually DISPATCHED for it (the governor let it
5295
+ * through) and the frame was not shed. Post-analysis reads it as "an empty
5296
+ * doormat on this frame is an ABSENCE, not silence" (D687). Never mutates.
5297
+ */
5298
+ function withPackageLook(result, dispatched) {
5299
+ if (result.shed === true) return result;
5300
+ return dispatched.some((s) => s.addonId === "package-detection" && s.enabled !== false) ? {
5301
+ ...result,
5302
+ packageLooked: true
5303
+ } : result;
5304
+ }
5305
+ /**
5293
5306
  * Burst: 5 runs, 5s apart — 0s, 5s, 10s, 15s, 20s after the settle edge.
5294
5307
  *
5295
5308
  * Sized as a SAFETY NET when the operator raises `packageDropDwellSec` above
@@ -11377,7 +11390,8 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
11377
11390
  return null;
11378
11391
  }
11379
11392
  if (result === null) return null;
11380
- return this.recoverSmallSubjects(deviceId, frame, result, dispatchSteps, camConfig?.deviceKey);
11393
+ const looked = withPackageLook(result, dispatchSteps);
11394
+ return this.recoverSmallSubjects(deviceId, frame, looked, dispatchSteps, camConfig?.deviceKey);
11381
11395
  }
11382
11396
  /**
11383
11397
  * The second detector pass at native resolution — `small-subject-recovery.ts`
@@ -6777,6 +6777,30 @@ async function patchExport(state, id, patch) {
6777
6777
  return merged;
6778
6778
  });
6779
6779
  }
6780
+ /**
6781
+ * Patch a row UNLESS its current state says not to — the check and the write
6782
+ * are ONE serialized step, so a delete that lands between a separate read and
6783
+ * a separate patch can no longer be overwritten (review R5).
6784
+ */
6785
+ async function patchExportUnless(state, id, patch, refuse) {
6786
+ return serialize(state, id, async () => {
6787
+ const existing = await state.read(id);
6788
+ if (existing === null) return { kind: "missing" };
6789
+ if (refuse(existing)) return {
6790
+ kind: "refused",
6791
+ record: existing
6792
+ };
6793
+ const merged = require_dist.ExportRecordSchema.parse({
6794
+ ...existing,
6795
+ ...patch
6796
+ });
6797
+ await state.write(id, merged);
6798
+ return {
6799
+ kind: "patched",
6800
+ record: merged
6801
+ };
6802
+ });
6803
+ }
6780
6804
  /** Read one export row by id — ONE keyed row read. */
6781
6805
  async function getExportRecord(state, id) {
6782
6806
  return state.read(id);
@@ -6996,6 +7020,13 @@ function expectedOutSeconds(rec) {
6996
7020
  }
6997
7021
  return rangeSec / (rec.options.speed ?? 1);
6998
7022
  }
7023
+ /**
7024
+ * A row nobody will read: its caller deleted it, it expired, or it already
7025
+ * failed (a cancel finalises as `failed`). The engine renders none of these.
7026
+ */
7027
+ function isAbandonedExportState(state) {
7028
+ return state === "deleted" || state === "expired" || state === "failed";
7029
+ }
6999
7030
  var ExportEngine = class {
7000
7031
  deps;
7001
7032
  queue = [];
@@ -7056,16 +7087,37 @@ var ExportEngine = class {
7056
7087
  async render(id) {
7057
7088
  const existing = await getExportRecord(this.deps.state, id);
7058
7089
  if (!existing) return;
7090
+ if (isAbandonedExportState(existing.state)) {
7091
+ this.deps.logger.info("export render skipped: the export was abandoned before it ran", {
7092
+ tags: { deviceId: existing.deviceId },
7093
+ meta: {
7094
+ exportId: id,
7095
+ state: existing.state
7096
+ }
7097
+ });
7098
+ return;
7099
+ }
7059
7100
  const subject = require_dist.exportSubjectOf(existing);
7060
7101
  if (subject.kind === "clip" && subject.source !== ANALYTICS_CLIP_SOURCE) {
7061
7102
  await this.fetchClip(id, existing, subject);
7062
7103
  return;
7063
7104
  }
7064
- const rec = await patchExport(this.deps.state, id, {
7105
+ const claimed = await patchExportUnless(this.deps.state, id, {
7065
7106
  state: "rendering",
7066
7107
  progressPct: 0
7067
- });
7068
- if (!rec) return;
7108
+ }, (current) => isAbandonedExportState(current.state));
7109
+ if (claimed.kind === "missing") return;
7110
+ if (claimed.kind === "refused") {
7111
+ this.deps.logger.info("export render skipped: the export was abandoned before it ran", {
7112
+ tags: { deviceId: claimed.record.deviceId },
7113
+ meta: {
7114
+ exportId: id,
7115
+ state: claimed.record.state
7116
+ }
7117
+ });
7118
+ return;
7119
+ }
7120
+ const rec = claimed.record;
7069
7121
  const playlist = await this.deps.resolvePlaylist(rec);
7070
7122
  if (!playlist) {
7071
7123
  await this.fail(id, "no footage for range");
@@ -7699,6 +7751,19 @@ var ExportEngine = class {
7699
7751
  await this.fail(id, `render produced no video frames (${bytes} bytes, < ${EXPORT_MIN_PLAUSIBLE_BYTES})`);
7700
7752
  return;
7701
7753
  }
7754
+ const current = await getExportRecord(this.deps.state, id);
7755
+ if (current !== null && isAbandonedExportState(current.state)) {
7756
+ await this.deps.removeFile(outPath);
7757
+ this.deps.logger.info("export render discarded: the export was abandoned while it ran", {
7758
+ tags: { deviceId: current.deviceId },
7759
+ meta: {
7760
+ exportId: id,
7761
+ state: current.state,
7762
+ bytes
7763
+ }
7764
+ });
7765
+ return;
7766
+ }
7702
7767
  await patchExport(this.deps.state, id, {
7703
7768
  state: "ready",
7704
7769
  progressPct: 100,
@@ -7726,6 +7791,8 @@ var ExportEngine = class {
7726
7791
  exportId: id,
7727
7792
  error
7728
7793
  } });
7794
+ const current = await getExportRecord(this.deps.state, id);
7795
+ if (current !== null && current.state === "deleted") return;
7729
7796
  await patchExport(this.deps.state, id, {
7730
7797
  state: "failed",
7731
7798
  progressPct: null,
@@ -6769,6 +6769,30 @@ async function patchExport(state, id, patch) {
6769
6769
  return merged;
6770
6770
  });
6771
6771
  }
6772
+ /**
6773
+ * Patch a row UNLESS its current state says not to — the check and the write
6774
+ * are ONE serialized step, so a delete that lands between a separate read and
6775
+ * a separate patch can no longer be overwritten (review R5).
6776
+ */
6777
+ async function patchExportUnless(state, id, patch, refuse) {
6778
+ return serialize(state, id, async () => {
6779
+ const existing = await state.read(id);
6780
+ if (existing === null) return { kind: "missing" };
6781
+ if (refuse(existing)) return {
6782
+ kind: "refused",
6783
+ record: existing
6784
+ };
6785
+ const merged = ExportRecordSchema.parse({
6786
+ ...existing,
6787
+ ...patch
6788
+ });
6789
+ await state.write(id, merged);
6790
+ return {
6791
+ kind: "patched",
6792
+ record: merged
6793
+ };
6794
+ });
6795
+ }
6772
6796
  /** Read one export row by id — ONE keyed row read. */
6773
6797
  async function getExportRecord(state, id) {
6774
6798
  return state.read(id);
@@ -6988,6 +7012,13 @@ function expectedOutSeconds(rec) {
6988
7012
  }
6989
7013
  return rangeSec / (rec.options.speed ?? 1);
6990
7014
  }
7015
+ /**
7016
+ * A row nobody will read: its caller deleted it, it expired, or it already
7017
+ * failed (a cancel finalises as `failed`). The engine renders none of these.
7018
+ */
7019
+ function isAbandonedExportState(state) {
7020
+ return state === "deleted" || state === "expired" || state === "failed";
7021
+ }
6991
7022
  var ExportEngine = class {
6992
7023
  deps;
6993
7024
  queue = [];
@@ -7048,16 +7079,37 @@ var ExportEngine = class {
7048
7079
  async render(id) {
7049
7080
  const existing = await getExportRecord(this.deps.state, id);
7050
7081
  if (!existing) return;
7082
+ if (isAbandonedExportState(existing.state)) {
7083
+ this.deps.logger.info("export render skipped: the export was abandoned before it ran", {
7084
+ tags: { deviceId: existing.deviceId },
7085
+ meta: {
7086
+ exportId: id,
7087
+ state: existing.state
7088
+ }
7089
+ });
7090
+ return;
7091
+ }
7051
7092
  const subject = exportSubjectOf(existing);
7052
7093
  if (subject.kind === "clip" && subject.source !== ANALYTICS_CLIP_SOURCE) {
7053
7094
  await this.fetchClip(id, existing, subject);
7054
7095
  return;
7055
7096
  }
7056
- const rec = await patchExport(this.deps.state, id, {
7097
+ const claimed = await patchExportUnless(this.deps.state, id, {
7057
7098
  state: "rendering",
7058
7099
  progressPct: 0
7059
- });
7060
- if (!rec) return;
7100
+ }, (current) => isAbandonedExportState(current.state));
7101
+ if (claimed.kind === "missing") return;
7102
+ if (claimed.kind === "refused") {
7103
+ this.deps.logger.info("export render skipped: the export was abandoned before it ran", {
7104
+ tags: { deviceId: claimed.record.deviceId },
7105
+ meta: {
7106
+ exportId: id,
7107
+ state: claimed.record.state
7108
+ }
7109
+ });
7110
+ return;
7111
+ }
7112
+ const rec = claimed.record;
7061
7113
  const playlist = await this.deps.resolvePlaylist(rec);
7062
7114
  if (!playlist) {
7063
7115
  await this.fail(id, "no footage for range");
@@ -7691,6 +7743,19 @@ var ExportEngine = class {
7691
7743
  await this.fail(id, `render produced no video frames (${bytes} bytes, < ${EXPORT_MIN_PLAUSIBLE_BYTES})`);
7692
7744
  return;
7693
7745
  }
7746
+ const current = await getExportRecord(this.deps.state, id);
7747
+ if (current !== null && isAbandonedExportState(current.state)) {
7748
+ await this.deps.removeFile(outPath);
7749
+ this.deps.logger.info("export render discarded: the export was abandoned while it ran", {
7750
+ tags: { deviceId: current.deviceId },
7751
+ meta: {
7752
+ exportId: id,
7753
+ state: current.state,
7754
+ bytes
7755
+ }
7756
+ });
7757
+ return;
7758
+ }
7694
7759
  await patchExport(this.deps.state, id, {
7695
7760
  state: "ready",
7696
7761
  progressPct: 100,
@@ -7718,6 +7783,8 @@ var ExportEngine = class {
7718
7783
  exportId: id,
7719
7784
  error
7720
7785
  } });
7786
+ const current = await getExportRecord(this.deps.state, id);
7787
+ if (current !== null && current.state === "deleted") return;
7721
7788
  await patchExport(this.deps.state, id, {
7722
7789
  state: "failed",
7723
7790
  progressPct: null,
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.277",
21
+ version: "1.2.278",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.277",
39
+ version: "1.2.278",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-7H0Oxf23.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-DZh9jQi-.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.309",
3
+ "version": "1.2.310",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",