@camstack/addon-post-analysis 1.2.154 → 1.2.155
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.
- package/dist/{dist-Ds6x9Agm.js → dist-CXY233l4.js} +72 -0
- package/dist/{dist-D6iC--Gh.mjs → dist-DqNo8CCq.mjs} +72 -0
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CnB4y-xY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-rrVQlXbu.mjs} +2 -2
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DdANt7Ia.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C8CZ3hb-.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BqnSOQO7.mjs → hostInit-Bfr4wfpP.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +313 -88
- package/dist/pipeline-analytics/index.mjs +311 -106
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -8
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-CXY233l4.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -113,6 +113,7 @@ function actionTtlMs(subject) {
|
|
|
113
113
|
case "alarm": return subject.action === "disarm" ? ALARM_DISARM_TTL_MS : DEFAULT_ACTION_TTL_MS;
|
|
114
114
|
case "sequence": return DEFAULT_ACTION_TTL_MS;
|
|
115
115
|
case "addon-update": return DEFAULT_ACTION_TTL_MS;
|
|
116
|
+
case "server-update": return DEFAULT_ACTION_TTL_MS;
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
var NcActionTokenRegistry = class {
|
|
@@ -182,9 +183,9 @@ var NcActionTokenRegistry = class {
|
|
|
182
183
|
/**
|
|
183
184
|
* What a spent grant DID, in the operator's own terms.
|
|
184
185
|
*
|
|
185
|
-
* Per `kind`, because the
|
|
186
|
+
* Per `kind`, because the families answer different questions and one
|
|
186
187
|
* generic "Done" would leave a snooze indistinguishable from an open gate on
|
|
187
|
-
* the page a tap lands on. The switch is exhaustive by construction — a
|
|
188
|
+
* the page a tap lands on. The switch is exhaustive by construction — a new
|
|
188
189
|
* grant family will not compile until it has a sentence here.
|
|
189
190
|
*/
|
|
190
191
|
function describeGrant(grant) {
|
|
@@ -193,6 +194,7 @@ function describeGrant(grant) {
|
|
|
193
194
|
case "snooze": return grant.classes === void 0 || grant.classes.length === 0 ? `Silenced for ${grant.minutes} min` : `Silenced for ${grant.minutes} min — ${grant.classes.join(", ")}`;
|
|
194
195
|
case "alarm": return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
|
|
195
196
|
case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
|
|
197
|
+
case "server-update": return `Server update on ${grant.nodeId}`;
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
/** The audit line's `meta`, per family. A snooze that logged a `sequence` it
|
|
@@ -228,6 +230,10 @@ function grantMeta(grant) {
|
|
|
228
230
|
nodeId: grant.nodeId,
|
|
229
231
|
packages: grant.packages
|
|
230
232
|
};
|
|
233
|
+
case "server-update": return {
|
|
234
|
+
...common,
|
|
235
|
+
nodeId: grant.nodeId
|
|
236
|
+
};
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
/** What the human standing in front of the page is told. */
|
|
@@ -5515,6 +5521,37 @@ function buildAddonUpdateButtons(input) {
|
|
|
5515
5521
|
}];
|
|
5516
5522
|
}
|
|
5517
5523
|
//#endregion
|
|
5524
|
+
//#region src/notification-center/server-update-buttons.ts
|
|
5525
|
+
/** The id this button travels under — in the callback and in the audit line. */
|
|
5526
|
+
var SERVER_UPDATE_BUTTON_ID = "server-update-apply";
|
|
5527
|
+
function serverUpdateButtonRefusal(subject, recipient) {
|
|
5528
|
+
if (subject.kind !== "server-update-available") return "not-a-server-update";
|
|
5529
|
+
if (subject.nodeId === void 0) return "several-nodes";
|
|
5530
|
+
if (recipient.ownerUserId === void 0) return "unowned-target";
|
|
5531
|
+
return null;
|
|
5532
|
+
}
|
|
5533
|
+
/**
|
|
5534
|
+
* The button, or none.
|
|
5535
|
+
*
|
|
5536
|
+
* Returns an array rather than a nullable, so the caller composes it with the
|
|
5537
|
+
* other families by spreading — and so "which buttons survived" has one shape.
|
|
5538
|
+
*/
|
|
5539
|
+
function buildServerUpdateButtons(input) {
|
|
5540
|
+
const subject = input.subject;
|
|
5541
|
+
if (serverUpdateButtonRefusal(subject, input.recipient) !== null) return [];
|
|
5542
|
+
const nodeId = subject.nodeId ?? "";
|
|
5543
|
+
return [{
|
|
5544
|
+
id: SERVER_UPDATE_BUTTON_ID,
|
|
5545
|
+
label: input.label({ nodeId }),
|
|
5546
|
+
url: input.mintUrl({
|
|
5547
|
+
actionId: SERVER_UPDATE_BUTTON_ID,
|
|
5548
|
+
nodeId
|
|
5549
|
+
}),
|
|
5550
|
+
destructive: true,
|
|
5551
|
+
mode: "background"
|
|
5552
|
+
}];
|
|
5553
|
+
}
|
|
5554
|
+
//#endregion
|
|
5518
5555
|
//#region src/notification-center/audio-rule-matcher.ts
|
|
5519
5556
|
/**
|
|
5520
5557
|
* AudioWatcher — the Notification Center's matcher for audio rules
|
|
@@ -8596,6 +8633,13 @@ var en_default = {
|
|
|
8596
8633
|
"action.addon-update.partial": "{{nodeId}}: {{applied}} updated, {{skipped}} already current, {{failed}} failed",
|
|
8597
8634
|
"action.addon-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
|
|
8598
8635
|
"action.addon-update.unknown": "Could not read what needs updating on {{nodeId}} — nothing was changed. Open CamStack.",
|
|
8636
|
+
"button.server-update.apply": "Update server on {{nodeId}}",
|
|
8637
|
+
"action.server-update.title": "Server update",
|
|
8638
|
+
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
8639
|
+
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
8640
|
+
"action.server-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
|
|
8641
|
+
"action.server-update.unknown": "Could not read whether {{nodeId}} still needs a server update — nothing was changed. Open CamStack.",
|
|
8642
|
+
"action.server-update.failed": "Server update on {{nodeId}} was refused — nothing was changed. Open CamStack.",
|
|
8599
8643
|
"summary.title": "Summary — {{rule}}",
|
|
8600
8644
|
"summary.body.window": "{{from}}→{{to}}",
|
|
8601
8645
|
"summary.body.events": {
|
|
@@ -8834,6 +8878,13 @@ var it_default = {
|
|
|
8834
8878
|
"action.addon-update.partial": "{{nodeId}}: {{applied}} aggiornati, {{skipped}} già aggiornati, {{failed}} falliti",
|
|
8835
8879
|
"action.addon-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
|
|
8836
8880
|
"action.addon-update.unknown": "Non è stato possibile leggere cosa aggiornare su {{nodeId}} — non è stato cambiato nulla. Apri CamStack.",
|
|
8881
|
+
"button.server-update.apply": "Aggiorna server su {{nodeId}}",
|
|
8882
|
+
"action.server-update.title": "Aggiornamento server",
|
|
8883
|
+
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
8884
|
+
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
8885
|
+
"action.server-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
|
|
8886
|
+
"action.server-update.unknown": "Non è stato possibile leggere se {{nodeId}} ha ancora bisogno di un aggiornamento server — non è stato cambiato nulla. Apri CamStack.",
|
|
8887
|
+
"action.server-update.failed": "L'aggiornamento server su {{nodeId}} è stato rifiutato — non è stato cambiato nulla. Apri CamStack.",
|
|
8837
8888
|
"summary.title": "Riepilogo — {{rule}}",
|
|
8838
8889
|
"summary.body.window": "{{from}}→{{to}}",
|
|
8839
8890
|
"summary.body.events": {
|
|
@@ -20389,6 +20440,41 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20389
20440
|
packages: systemEvent?.packageNames ?? []
|
|
20390
20441
|
}
|
|
20391
20442
|
});
|
|
20443
|
+
const serverRefusal = systemEvent === void 0 ? "not-a-server-update" : serverUpdateButtonRefusal(systemEvent, recipient);
|
|
20444
|
+
const serverButtons = systemEvent === void 0 ? [] : buildServerUpdateButtons({
|
|
20445
|
+
subject: systemEvent,
|
|
20446
|
+
recipient,
|
|
20447
|
+
label: ({ nodeId }) => this.texts.text({
|
|
20448
|
+
key: "button.server-update.apply",
|
|
20449
|
+
vars: { nodeId }
|
|
20450
|
+
}),
|
|
20451
|
+
mintUrl: ({ actionId, nodeId }) => {
|
|
20452
|
+
const id = (0, node_crypto.randomUUID)();
|
|
20453
|
+
drafts.push({
|
|
20454
|
+
id,
|
|
20455
|
+
ttlMs: actionTtlMs({ kind: "server-update" }),
|
|
20456
|
+
grant: (expiresAt) => ({
|
|
20457
|
+
kind: "server-update",
|
|
20458
|
+
id,
|
|
20459
|
+
nodeId,
|
|
20460
|
+
deviceId: input.deviceId,
|
|
20461
|
+
actionId,
|
|
20462
|
+
targetId: input.targetId,
|
|
20463
|
+
expiresAt
|
|
20464
|
+
})
|
|
20465
|
+
});
|
|
20466
|
+
return id;
|
|
20467
|
+
}
|
|
20468
|
+
});
|
|
20469
|
+
if (serverRefusal === "several-nodes" || serverRefusal === "unowned-target") this.logger.info("server-update notification carries no apply button", {
|
|
20470
|
+
tags: { deviceId: input.deviceId },
|
|
20471
|
+
meta: {
|
|
20472
|
+
ruleId: input.ruleId,
|
|
20473
|
+
reason: serverRefusal,
|
|
20474
|
+
targetId: input.targetId,
|
|
20475
|
+
nodeIds: systemEvent?.nodeIds ?? []
|
|
20476
|
+
}
|
|
20477
|
+
});
|
|
20392
20478
|
if (alarmKind !== null && panel === null) this.logger.info("alarm notification carries no buttons — this node owns no panel", {
|
|
20393
20479
|
tags: { deviceId: input.deviceId },
|
|
20394
20480
|
meta: {
|
|
@@ -20400,7 +20486,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20400
20486
|
...sequenceButtons,
|
|
20401
20487
|
...snoozeButtons,
|
|
20402
20488
|
...alarmButtons,
|
|
20403
|
-
...updateButtons
|
|
20489
|
+
...updateButtons,
|
|
20490
|
+
...serverButtons
|
|
20404
20491
|
];
|
|
20405
20492
|
if (out.length === 0) return [];
|
|
20406
20493
|
const now = this.now();
|
|
@@ -20522,6 +20609,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20522
20609
|
case "alarm": return this.applyGrantedAlarm(grant);
|
|
20523
20610
|
case "sequence": return this.runGrantedSequence(grant);
|
|
20524
20611
|
case "addon-update": return this.applyGrantedAddonUpdate(grant);
|
|
20612
|
+
case "server-update": return this.applyGrantedServerUpdate(grant);
|
|
20525
20613
|
}
|
|
20526
20614
|
}
|
|
20527
20615
|
/**
|
|
@@ -20632,6 +20720,80 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20632
20720
|
if (applied.length === 0 && failed.length > 0) throw new Error(`no package updated on ${grant.nodeId} (${failed.join(", ")})`);
|
|
20633
20721
|
}
|
|
20634
20722
|
/**
|
|
20723
|
+
* Stage the server update one notification was about.
|
|
20724
|
+
*
|
|
20725
|
+
* The shape matches the addon-update runner, with one extra constraint: the
|
|
20726
|
+
* **start announcement leaves BEFORE apply**. Applying the hub kills this
|
|
20727
|
+
* process; a tap that went into silence because the process died mid-apply
|
|
20728
|
+
* is worse than no button. The reboot itself is confirmed by the existing
|
|
20729
|
+
* `server-updated` event on reconnect — this method does not wait for it.
|
|
20730
|
+
*
|
|
20731
|
+
* Throws when nothing was staged and something went wrong, so the plane's
|
|
20732
|
+
* line says the run failed rather than reporting a spent token as a success.
|
|
20733
|
+
*/
|
|
20734
|
+
async applyGrantedServerUpdate(grant) {
|
|
20735
|
+
const apply = this.deps.applyServerUpdate;
|
|
20736
|
+
const check = this.deps.checkServerUpdate;
|
|
20737
|
+
if (apply === void 0 || check === void 0) throw new Error("no server updater is wired on this node");
|
|
20738
|
+
let stillAvailable;
|
|
20739
|
+
try {
|
|
20740
|
+
stillAvailable = await check(grant.nodeId);
|
|
20741
|
+
} catch (err) {
|
|
20742
|
+
this.logger.warn("server update button changed nothing — could not read whether an update is still needed", { meta: {
|
|
20743
|
+
nodeId: grant.nodeId,
|
|
20744
|
+
error: String(err)
|
|
20745
|
+
} });
|
|
20746
|
+
await this.announceServerUpdate(grant, "unknown", { nodeId: grant.nodeId });
|
|
20747
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
20748
|
+
}
|
|
20749
|
+
if (!stillAvailable) {
|
|
20750
|
+
this.logger.info("server update button applied nothing — already current", { meta: { nodeId: grant.nodeId } });
|
|
20751
|
+
await this.announceServerUpdate(grant, "uptodate", { nodeId: grant.nodeId });
|
|
20752
|
+
return;
|
|
20753
|
+
}
|
|
20754
|
+
await this.announceServerUpdate(grant, "started", { nodeId: grant.nodeId });
|
|
20755
|
+
let result;
|
|
20756
|
+
try {
|
|
20757
|
+
result = await apply({ nodeId: grant.nodeId });
|
|
20758
|
+
} catch (err) {
|
|
20759
|
+
this.logger.warn("server update from a notification button threw", { meta: {
|
|
20760
|
+
nodeId: grant.nodeId,
|
|
20761
|
+
error: String(err)
|
|
20762
|
+
} });
|
|
20763
|
+
await this.announceServerUpdate(grant, "failed", { nodeId: grant.nodeId });
|
|
20764
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
20765
|
+
}
|
|
20766
|
+
if (!result.ok) {
|
|
20767
|
+
this.logger.warn("server update from a notification button was refused", { meta: {
|
|
20768
|
+
nodeId: grant.nodeId,
|
|
20769
|
+
error: result.error
|
|
20770
|
+
} });
|
|
20771
|
+
await this.announceServerUpdate(grant, "failed", { nodeId: grant.nodeId });
|
|
20772
|
+
throw new Error(result.error);
|
|
20773
|
+
}
|
|
20774
|
+
this.logger.info("server update staged from a notification button", { meta: { nodeId: grant.nodeId } });
|
|
20775
|
+
await this.announceServerUpdate(grant, "restarting", { nodeId: grant.nodeId });
|
|
20776
|
+
}
|
|
20777
|
+
/**
|
|
20778
|
+
* One sentence about a server-update tap, to the target the notification
|
|
20779
|
+
* went to. Best-effort — `deliverAnnouncement` never throws — because an
|
|
20780
|
+
* announcement that failed must not turn a staging that HAPPENED into a run
|
|
20781
|
+
* the plane reports as failed.
|
|
20782
|
+
*/
|
|
20783
|
+
async announceServerUpdate(grant, sentence, vars) {
|
|
20784
|
+
const targetId = grant.targetId;
|
|
20785
|
+
if (targetId === void 0) return;
|
|
20786
|
+
await this.dispatcher.deliverAnnouncement({
|
|
20787
|
+
reason: `server-update-${sentence}`,
|
|
20788
|
+
targetId,
|
|
20789
|
+
title: this.texts.text({ key: "action.server-update.title" }),
|
|
20790
|
+
body: this.texts.text({
|
|
20791
|
+
key: `action.server-update.${sentence}`,
|
|
20792
|
+
vars
|
|
20793
|
+
})
|
|
20794
|
+
});
|
|
20795
|
+
}
|
|
20796
|
+
/**
|
|
20635
20797
|
* One sentence about an update tap, to the target the notification went to.
|
|
20636
20798
|
*
|
|
20637
20799
|
* Best-effort by construction — `deliverAnnouncement` never throws and never
|
|
@@ -26095,7 +26257,7 @@ var DEFAULT_MEDIA_LOCATION = "eventMedia";
|
|
|
26095
26257
|
* whose contract is disposal.** The gallery's row lives on the data volume;
|
|
26096
26258
|
* its blob belongs there too, and now does. See D197.
|
|
26097
26259
|
*/
|
|
26098
|
-
var GALLERY_MEDIA_LOCATION = "
|
|
26260
|
+
var GALLERY_MEDIA_LOCATION = "addon-data";
|
|
26099
26261
|
/**
|
|
26100
26262
|
* The location a new blob is written to, derived from its owner kind.
|
|
26101
26263
|
*
|
|
@@ -26992,7 +27154,7 @@ var MediaStore = class {
|
|
|
26992
27154
|
for (const row of rows) {
|
|
26993
27155
|
const data = row.data;
|
|
26994
27156
|
const from = mediaRowLocation(data);
|
|
26995
|
-
if (from === "
|
|
27157
|
+
if (from === "addon-data") continue;
|
|
26996
27158
|
const relativePath = String(data["path"] ?? "");
|
|
26997
27159
|
const deviceId = Number(data["deviceId"]);
|
|
26998
27160
|
if (relativePath.length === 0) continue;
|
|
@@ -35938,6 +36100,7 @@ async function runMediaReclaim(input) {
|
|
|
35938
36100
|
byteBudget -= report.bytesReclaimed;
|
|
35939
36101
|
nextCursors.set(key, report.stoppedBy === "exhausted" ? 0 : report.nextCursor);
|
|
35940
36102
|
devices.push(report);
|
|
36103
|
+
input.onDevice?.(report);
|
|
35941
36104
|
if (report.examined > 0 || report.refused > 0) input.deps.logger.info("media reclaim window", {
|
|
35942
36105
|
tags: { deviceId },
|
|
35943
36106
|
meta: {
|
|
@@ -35977,14 +36140,12 @@ async function runMediaReclaim(input) {
|
|
|
35977
36140
|
/**
|
|
35978
36141
|
* The operator button for the debug-media reclaim.
|
|
35979
36142
|
*
|
|
35980
|
-
* The
|
|
35981
|
-
*
|
|
35982
|
-
*
|
|
35983
|
-
*
|
|
36143
|
+
* The pass is tens of minutes. The button STARTS it and returns
|
|
36144
|
+
* `{ started, alreadyRunning }` immediately — the same shape as the CLIP
|
|
36145
|
+
* rebuild button. Progress is `retention.mediaReclaimStatus`. Awaiting the
|
|
36146
|
+
* walk is how this door hit the 60 s UDS deadline.
|
|
35984
36147
|
*
|
|
35985
|
-
*
|
|
35986
|
-
* earned it: dry-run default, explicit pacing, explicit blast radius, and a
|
|
35987
|
-
* report you can read instead of a log line you have to find.
|
|
36148
|
+
* `mode` omitted means `report`. `reclaim` is a word that has to be typed.
|
|
35988
36149
|
*/
|
|
35989
36150
|
/**
|
|
35990
36151
|
* `report` counts; `reclaim` deletes.
|
|
@@ -36025,40 +36186,28 @@ var MediaReclaimActionInputSchema = require_dist.object({
|
|
|
36025
36186
|
*/
|
|
36026
36187
|
graceMinutes: require_dist.number().int().min(1).max(10080).optional()
|
|
36027
36188
|
});
|
|
36028
|
-
var
|
|
36029
|
-
|
|
36030
|
-
|
|
36031
|
-
examined: require_dist.number(),
|
|
36032
|
-
eligible: require_dist.number(),
|
|
36033
|
-
reclaimed: require_dist.number(),
|
|
36034
|
-
bytesReclaimed: require_dist.number(),
|
|
36035
|
-
/** Rows kept because the owning track is pinned (`staging`/`favourited`). */
|
|
36036
|
-
held: require_dist.number(),
|
|
36037
|
-
/** Rows kept because the owner could not be shown to be a motion event. */
|
|
36038
|
-
unresolved: require_dist.number(),
|
|
36039
|
-
withinGrace: require_dist.number(),
|
|
36040
|
-
/** Deletes that REFUSED — the blob survived or the volume could not answer.
|
|
36041
|
-
* The row was kept and the cursor did not pass it. */
|
|
36042
|
-
refused: require_dist.number(),
|
|
36043
|
-
stoppedBy: require_dist.string(),
|
|
36044
|
-
/** Where the NEXT pass resumes. `0` = this device+scope was walked to the end. */
|
|
36045
|
-
nextCursor: require_dist.number()
|
|
36189
|
+
var MediaReclaimStartedSchema = require_dist.object({
|
|
36190
|
+
started: require_dist.boolean(),
|
|
36191
|
+
alreadyRunning: require_dist.boolean()
|
|
36046
36192
|
});
|
|
36047
|
-
var
|
|
36048
|
-
|
|
36049
|
-
|
|
36050
|
-
mode: require_dist._enum(["report", "reclaim"]),
|
|
36051
|
-
devices: require_dist.array(MediaReclaimDeviceReportSchema),
|
|
36193
|
+
var MediaReclaimStatusSchema = require_dist.object({
|
|
36194
|
+
running: require_dist.boolean(),
|
|
36195
|
+
mode: require_dist._enum(["report", "reclaim"]).nullable(),
|
|
36052
36196
|
totalExamined: require_dist.number(),
|
|
36053
36197
|
totalEligible: require_dist.number(),
|
|
36054
36198
|
totalReclaimed: require_dist.number(),
|
|
36055
36199
|
totalBytesReclaimed: require_dist.number(),
|
|
36056
36200
|
totalRefused: require_dist.number(),
|
|
36057
|
-
|
|
36058
|
-
|
|
36059
|
-
|
|
36201
|
+
devicesDone: require_dist.number(),
|
|
36202
|
+
complete: require_dist.boolean().nullable(),
|
|
36203
|
+
startedAtMs: require_dist.number().nullable(),
|
|
36204
|
+
finishedAtMs: require_dist.number().nullable(),
|
|
36205
|
+
error: require_dist.string().nullable()
|
|
36206
|
+
});
|
|
36207
|
+
var mediaReclaimActions = require_dist.defineCustomActions({
|
|
36208
|
+
"retention.mediaReclaim": require_dist.customAction(MediaReclaimActionInputSchema, MediaReclaimStartedSchema, { auth: "admin" }),
|
|
36209
|
+
"retention.mediaReclaimStatus": require_dist.customAction(require_dist.object({}), MediaReclaimStatusSchema, { auth: "admin" })
|
|
36060
36210
|
});
|
|
36061
|
-
var mediaReclaimActions = require_dist.defineCustomActions({ "retention.mediaReclaim": require_dist.customAction(MediaReclaimActionInputSchema, MediaReclaimActionResultSchema, { auth: "admin" }) });
|
|
36062
36211
|
//#endregion
|
|
36063
36212
|
//#region src/pipeline-analytics/retention/orphan-audit-actions.ts
|
|
36064
36213
|
/**
|
|
@@ -41151,6 +41300,7 @@ function createLocationAwareMediaStorage(deps) {
|
|
|
41151
41300
|
const roots = /* @__PURE__ */ new Map();
|
|
41152
41301
|
let defaultBypassInvalidated = false;
|
|
41153
41302
|
const rootOf = async (locationId) => {
|
|
41303
|
+
if (deps.galleryLocationId !== void 0 && deps.galleryRoot !== void 0 && locationId === deps.galleryLocationId) return deps.galleryRoot;
|
|
41154
41304
|
const cached = roots.get(locationId);
|
|
41155
41305
|
if (cached !== void 0) return cached;
|
|
41156
41306
|
const root = await deps.resolveRoot(locationId);
|
|
@@ -43657,7 +43807,7 @@ var PhotoAnalysisSessions = class {
|
|
|
43657
43807
|
* a magnet that recognises every degraded probe as its owner, and 22 of 50
|
|
43658
43808
|
* live samples were enrolled that way on 2026-08-18;
|
|
43659
43809
|
* 3. both blobs are written under `ownerKind: 'identity'`, which is what puts
|
|
43660
|
-
* them
|
|
43810
|
+
* them in the addon data dir — the location a footage wipe cannot reach (D197);
|
|
43661
43811
|
* 4. the row is written last, and if it does not land BOTH blobs are reclaimed.
|
|
43662
43812
|
* An orphan under `identity` is exempt from every sweep this repo has, so
|
|
43663
43813
|
* it would otherwise live forever.
|
|
@@ -64177,6 +64327,20 @@ var TIMELAPSE_TRACK_QUERY_LIMIT = 2e3;
|
|
|
64177
64327
|
* window rather than a random slice of it.
|
|
64178
64328
|
*/
|
|
64179
64329
|
var TIMELAPSE_EVENT_QUERY_LIMIT = 1e4;
|
|
64330
|
+
var IDLE_MEDIA_RECLAIM = {
|
|
64331
|
+
running: false,
|
|
64332
|
+
mode: null,
|
|
64333
|
+
totalExamined: 0,
|
|
64334
|
+
totalEligible: 0,
|
|
64335
|
+
totalReclaimed: 0,
|
|
64336
|
+
totalBytesReclaimed: 0,
|
|
64337
|
+
totalRefused: 0,
|
|
64338
|
+
devicesDone: 0,
|
|
64339
|
+
complete: null,
|
|
64340
|
+
startedAtMs: null,
|
|
64341
|
+
finishedAtMs: null,
|
|
64342
|
+
error: null
|
|
64343
|
+
};
|
|
64180
64344
|
var PIPELINE_ANALYTICS_ADDON_ID = "pipeline-analytics";
|
|
64181
64345
|
/** Artefact index ⇄ shelf reconcile cadence. Hourly: the two only drift on the
|
|
64182
64346
|
* bounds' own timescale (14 days / 2000 entries / 2 GiB). */
|
|
@@ -65292,9 +65456,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
65292
65456
|
* In RAM like the audit's: the pass is resumable from zero at no cost
|
|
65293
65457
|
* beyond re-walking a head that is already empty. */
|
|
65294
65458
|
mediaReclaimCursors = /* @__PURE__ */ new Map();
|
|
65295
|
-
/**
|
|
65296
|
-
|
|
65297
|
-
mediaReclaimRunning = false;
|
|
65459
|
+
/** Live state of the detached debug-media reclaim — see `reclaimDebugMedia`. */
|
|
65460
|
+
mediaReclaimState = { ...IDLE_MEDIA_RECLAIM };
|
|
65298
65461
|
/**
|
|
65299
65462
|
* The vector-index leg's resume cursor, kept in its OWN field.
|
|
65300
65463
|
*
|
|
@@ -65734,6 +65897,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
65734
65897
|
const routed = createLocationAwareMediaStorage({
|
|
65735
65898
|
base: storage,
|
|
65736
65899
|
defaultLocation: "eventMedia",
|
|
65900
|
+
galleryLocationId: GALLERY_MEDIA_LOCATION,
|
|
65901
|
+
galleryRoot: node_path.default.join(this.ctx.dataDir, "gallery"),
|
|
65737
65902
|
resolveRoot: (locationId) => this.ctx.api.storage.resolve.query({
|
|
65738
65903
|
location: locationId,
|
|
65739
65904
|
relativePath: ""
|
|
@@ -66410,6 +66575,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
66410
66575
|
};
|
|
66411
66576
|
}
|
|
66412
66577
|
},
|
|
66578
|
+
checkServerUpdate: async (nodeId) => {
|
|
66579
|
+
const result = await this.ctx.api.serverManagement.checkServerUpdate.mutate({ nodeId });
|
|
66580
|
+
if (result.error) throw new Error(result.error);
|
|
66581
|
+
return result.updateAvailable;
|
|
66582
|
+
},
|
|
66583
|
+
applyServerUpdate: async ({ nodeId }) => {
|
|
66584
|
+
try {
|
|
66585
|
+
const result = await this.ctx.api.serverManagement.applyServerUpdate.mutate({ nodeId });
|
|
66586
|
+
if (!result.accepted) return {
|
|
66587
|
+
ok: false,
|
|
66588
|
+
error: result.message
|
|
66589
|
+
};
|
|
66590
|
+
return { ok: true };
|
|
66591
|
+
} catch (err) {
|
|
66592
|
+
return {
|
|
66593
|
+
ok: false,
|
|
66594
|
+
error: require_dist.errMsg(err)
|
|
66595
|
+
};
|
|
66596
|
+
}
|
|
66597
|
+
},
|
|
66413
66598
|
readDeviceStates: async (ids) => {
|
|
66414
66599
|
const out = /* @__PURE__ */ new Map();
|
|
66415
66600
|
for (const id of ids) try {
|
|
@@ -67342,19 +67527,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
67342
67527
|
async reconcileGalleryMediaLocation() {
|
|
67343
67528
|
const mediaStore = this.mediaStore;
|
|
67344
67529
|
if (!mediaStore) return;
|
|
67345
|
-
try {
|
|
67346
|
-
await this.ctx.api.storage.resolve.query({
|
|
67347
|
-
location: GALLERY_MEDIA_LOCATION,
|
|
67348
|
-
relativePath: ""
|
|
67349
|
-
});
|
|
67350
|
-
} catch (err) {
|
|
67351
|
-
this.ctx.logger.error("gallery media location is NOT available — enrolling a face, a plate or a scene reference will FAIL until it is", { meta: {
|
|
67352
|
-
location: GALLERY_MEDIA_LOCATION,
|
|
67353
|
-
error: require_dist.errMsg(err),
|
|
67354
|
-
hint: "the declaration is seeded when a storage-provider registers; restart this node so the manifest is re-read (D197). Refusing the write is deliberate — the alternative is the footage volume, which is where the last gallery was lost."
|
|
67355
|
-
} });
|
|
67356
|
-
return;
|
|
67357
|
-
}
|
|
67358
67530
|
try {
|
|
67359
67531
|
const result = await mediaStore.relocateExemptMediaToGallery();
|
|
67360
67532
|
if (result.relocated === 0 && result.missing === 0 && result.failed === 0) return;
|
|
@@ -72423,42 +72595,58 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72423
72595
|
"embedding.reembedFaces": async (input) => this.runFaceReembedPass(input),
|
|
72424
72596
|
"embedding.reembedFacesStatus": async () => this.getFaceReembedStatus(),
|
|
72425
72597
|
"retention.orphanAudit": async (input) => this.runOrphanAuditNow(input),
|
|
72426
|
-
"retention.mediaReclaim": async (input) => this.
|
|
72598
|
+
"retention.mediaReclaim": async (input) => this.reclaimDebugMedia(input),
|
|
72599
|
+
"retention.mediaReclaimStatus": async () => this.getMediaReclaimStatus()
|
|
72427
72600
|
};
|
|
72428
72601
|
}
|
|
72429
72602
|
/**
|
|
72430
|
-
*
|
|
72431
|
-
* `retention.mediaReclaim`.
|
|
72432
|
-
*
|
|
72433
|
-
* DRY RUN BY DEFAULT. Draining the standing 111 GB means running this with
|
|
72434
|
-
* `mode: 'reclaim'` repeatedly until `complete` is true; each run resumes
|
|
72435
|
-
* from the per-device cursors this method keeps, and a run that is cut short
|
|
72436
|
-
* has still only forgotten rows whose blobs it proved gone.
|
|
72603
|
+
* Start a debug-media reclaim pass. DETACHED.
|
|
72437
72604
|
*
|
|
72438
|
-
*
|
|
72439
|
-
*
|
|
72440
|
-
*
|
|
72605
|
+
* Returns `{ started }` immediately. The walk is tens of minutes at 2 MB/s;
|
|
72606
|
+
* awaiting it is a timeout, not a verdict. Poll {@link getMediaReclaimStatus}.
|
|
72607
|
+
* Dry-run unless `mode: 'reclaim'`. A second call while one is in flight is
|
|
72608
|
+
* `alreadyRunning`, not a second walk on the same spindle.
|
|
72441
72609
|
*/
|
|
72442
|
-
async
|
|
72610
|
+
async reclaimDebugMedia(rawInput) {
|
|
72443
72611
|
const input = MediaReclaimActionInputSchema.parse(rawInput);
|
|
72612
|
+
if (this.mediaReclaimState.running) {
|
|
72613
|
+
this.ctx.logger.info("debug-media reclaim already running — request ignored");
|
|
72614
|
+
return {
|
|
72615
|
+
started: false,
|
|
72616
|
+
alreadyRunning: true
|
|
72617
|
+
};
|
|
72618
|
+
}
|
|
72444
72619
|
const mediaStore = this.mediaStore;
|
|
72445
72620
|
const eventStore = this.eventStore;
|
|
72446
72621
|
const trackStore = this.trackStore;
|
|
72447
|
-
|
|
72448
|
-
|
|
72622
|
+
if (!mediaStore || !eventStore || !trackStore) {
|
|
72623
|
+
this.ctx.logger.warn("reclaimDebugMedia: analytics stores unavailable");
|
|
72624
|
+
return {
|
|
72625
|
+
started: false,
|
|
72626
|
+
alreadyRunning: false
|
|
72627
|
+
};
|
|
72628
|
+
}
|
|
72629
|
+
this.mediaReclaimState = {
|
|
72630
|
+
...IDLE_MEDIA_RECLAIM,
|
|
72631
|
+
running: true,
|
|
72449
72632
|
mode: input.mode,
|
|
72450
|
-
|
|
72451
|
-
totalExamined: 0,
|
|
72452
|
-
totalEligible: 0,
|
|
72453
|
-
totalReclaimed: 0,
|
|
72454
|
-
totalBytesReclaimed: 0,
|
|
72455
|
-
totalRefused: 0,
|
|
72456
|
-
durationMs: 0,
|
|
72457
|
-
complete: false
|
|
72633
|
+
startedAtMs: Date.now()
|
|
72458
72634
|
};
|
|
72459
|
-
|
|
72460
|
-
|
|
72461
|
-
|
|
72635
|
+
this.ctx.logger.info("debug-media reclaim starting", { meta: {
|
|
72636
|
+
mode: input.mode,
|
|
72637
|
+
restart: input.restart === true
|
|
72638
|
+
} });
|
|
72639
|
+
this.executeMediaReclaim(input, mediaStore, eventStore, trackStore);
|
|
72640
|
+
return {
|
|
72641
|
+
started: true,
|
|
72642
|
+
alreadyRunning: false
|
|
72643
|
+
};
|
|
72644
|
+
}
|
|
72645
|
+
/**
|
|
72646
|
+
* The walk itself. Called DETACHED from {@link reclaimDebugMedia}; never
|
|
72647
|
+
* await this from an RPC.
|
|
72648
|
+
*/
|
|
72649
|
+
async executeMediaReclaim(input, mediaStore, eventStore, trackStore) {
|
|
72462
72650
|
try {
|
|
72463
72651
|
const limits = {
|
|
72464
72652
|
mode: input.mode,
|
|
@@ -72476,6 +72664,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72476
72664
|
scopes,
|
|
72477
72665
|
limits,
|
|
72478
72666
|
cursors: input.restart === true ? /* @__PURE__ */ new Map() : this.mediaReclaimCursors,
|
|
72667
|
+
onDevice: (device) => {
|
|
72668
|
+
const s = this.mediaReclaimState;
|
|
72669
|
+
if (!s.running) return;
|
|
72670
|
+
this.mediaReclaimState = {
|
|
72671
|
+
...s,
|
|
72672
|
+
totalExamined: s.totalExamined + device.examined,
|
|
72673
|
+
totalEligible: s.totalEligible + device.eligible,
|
|
72674
|
+
totalReclaimed: s.totalReclaimed + device.reclaimed,
|
|
72675
|
+
totalBytesReclaimed: s.totalBytesReclaimed + device.bytesReclaimed,
|
|
72676
|
+
totalRefused: s.totalRefused + device.refused,
|
|
72677
|
+
devicesDone: s.devicesDone + 1
|
|
72678
|
+
};
|
|
72679
|
+
},
|
|
72479
72680
|
deps: {
|
|
72480
72681
|
page: ({ deviceId, scope, afterMs, limit }) => mediaStore.pageByKindForDevice({
|
|
72481
72682
|
deviceId,
|
|
@@ -72502,15 +72703,39 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72502
72703
|
bytesReclaimed: report.totalBytesReclaimed,
|
|
72503
72704
|
detail: `debug-media reclaim (${scopes.join("+")}): ${report.totalReclaimed} rows, ${report.totalRefused} refused, complete=${report.complete}`
|
|
72504
72705
|
});
|
|
72505
|
-
|
|
72506
|
-
|
|
72507
|
-
|
|
72508
|
-
|
|
72706
|
+
this.mediaReclaimState = {
|
|
72707
|
+
...this.mediaReclaimState,
|
|
72708
|
+
running: false,
|
|
72709
|
+
totalExamined: report.totalExamined,
|
|
72710
|
+
totalEligible: report.totalEligible,
|
|
72711
|
+
totalReclaimed: report.totalReclaimed,
|
|
72712
|
+
totalBytesReclaimed: report.totalBytesReclaimed,
|
|
72713
|
+
totalRefused: report.totalRefused,
|
|
72714
|
+
complete: report.complete,
|
|
72715
|
+
finishedAtMs: Date.now()
|
|
72509
72716
|
};
|
|
72510
|
-
|
|
72511
|
-
|
|
72717
|
+
this.ctx.logger.info("debug-media reclaim finished", { meta: {
|
|
72718
|
+
mode: report.mode,
|
|
72719
|
+
examined: report.totalExamined,
|
|
72720
|
+
reclaimed: report.totalReclaimed,
|
|
72721
|
+
bytesReclaimed: report.totalBytesReclaimed,
|
|
72722
|
+
complete: report.complete
|
|
72723
|
+
} });
|
|
72724
|
+
} catch (err) {
|
|
72725
|
+
this.mediaReclaimState = {
|
|
72726
|
+
...this.mediaReclaimState,
|
|
72727
|
+
running: false,
|
|
72728
|
+
finishedAtMs: Date.now(),
|
|
72729
|
+
error: err instanceof Error ? err.message : String(err),
|
|
72730
|
+
complete: false
|
|
72731
|
+
};
|
|
72732
|
+
this.ctx.logger.warn("debug-media reclaim failed", { meta: { error: String(err) } });
|
|
72512
72733
|
}
|
|
72513
72734
|
}
|
|
72735
|
+
/** Progress of the detached reclaim — the only way to observe a long pass. */
|
|
72736
|
+
async getMediaReclaimStatus() {
|
|
72737
|
+
return { ...this.mediaReclaimState };
|
|
72738
|
+
}
|
|
72514
72739
|
/** Progress of the detached rebuild — the only way to observe a long pass. */
|
|
72515
72740
|
async getObjectEmbeddingRebuildStatus() {
|
|
72516
72741
|
const state = this.rebuildState;
|