@camstack/addon-post-analysis 1.2.641 → 1.2.642
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/{clip-model-registry-Cw7Iy7Qi.mjs → clip-model-registry-CoH7ORtC.mjs} +14 -1
- package/dist/{clip-model-registry-Qr0XipkX.js → clip-model-registry-Dj3lrvL2.js} +14 -1
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cct0TMzC.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Dhan9FH4.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-B9c2FowI.mjs → hostInit-j1gxJvS7.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +354 -6
- package/dist/pipeline-analytics/index.mjs +354 -6
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -48242,7 +48242,20 @@ var NcRuleInputSchema = object({
|
|
|
48242
48242
|
* save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
|
|
48243
48243
|
* path does not run Zod defaults.
|
|
48244
48244
|
*/
|
|
48245
|
-
groupingSec: number().int().min(5).max(60).optional()
|
|
48245
|
+
groupingSec: number().int().min(5).max(60).optional(),
|
|
48246
|
+
/**
|
|
48247
|
+
* ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
|
|
48248
|
+
* carries "Correct" / "Wrong" buttons (and, where the channel has room, a
|
|
48249
|
+
* "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
|
|
48250
|
+
* same mark the Library writes, and records whose notification it was and
|
|
48251
|
+
* when. A channel with no buttons gets the same two choices as links.
|
|
48252
|
+
*
|
|
48253
|
+
* `.optional()`, never `.default()` (a Zod default does not run on the addon
|
|
48254
|
+
* cap path): absent and `false` both mean off, which is what every rule
|
|
48255
|
+
* authored before this field meant. An editor turns it off by writing
|
|
48256
|
+
* `false`, so it needs no `clear` key.
|
|
48257
|
+
*/
|
|
48258
|
+
askVerdict: boolean().optional()
|
|
48246
48259
|
});
|
|
48247
48260
|
/**
|
|
48248
48261
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -48273,7 +48273,20 @@ var NcRuleInputSchema = object({
|
|
|
48273
48273
|
* save by the hub's grouping guard). `.optional()`, never `.default()`: the addon cap
|
|
48274
48274
|
* path does not run Zod defaults.
|
|
48275
48275
|
*/
|
|
48276
|
-
groupingSec: number().int().min(5).max(60).optional()
|
|
48276
|
+
groupingSec: number().int().min(5).max(60).optional(),
|
|
48277
|
+
/**
|
|
48278
|
+
* ASK ME TO CONFIRM DETECTIONS (#243): a DETECTION notification of this rule
|
|
48279
|
+
* carries "Correct" / "Wrong" buttons (and, where the channel has room, a
|
|
48280
|
+
* "Wrong, it is a <class>" choice). A tap marks the track for retraining, the
|
|
48281
|
+
* same mark the Library writes, and records whose notification it was and
|
|
48282
|
+
* when. A channel with no buttons gets the same two choices as links.
|
|
48283
|
+
*
|
|
48284
|
+
* `.optional()`, never `.default()` (a Zod default does not run on the addon
|
|
48285
|
+
* cap path): absent and `false` both mean off, which is what every rule
|
|
48286
|
+
* authored before this field meant. An editor turns it off by writing
|
|
48287
|
+
* `false`, so it needs no `clear` key.
|
|
48288
|
+
*/
|
|
48289
|
+
askVerdict: boolean().optional()
|
|
48277
48290
|
});
|
|
48278
48291
|
/**
|
|
48279
48292
|
* Partial patch for `updateRule` — any subset of the input fields, plus the
|
|
@@ -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-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-Dj3lrvL2.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Qi as BaseAddon, _r as embeddingRequirementsUnavailableError, a as CLIP_EMBEDDING_STEP_ID, da as normalizeNodeId, gi as resolvePoolMemoryPolicy, gr as embeddingRequirementsFailedError, i as CLIP_TEXT_MODELS, l as resolveClusterModelPin, mr as embeddingModelFailedError, n as DEFAULT_CLIP_MODEL, ni as parseProcStatus, pr as embeddingEncoderCapability, r as CLIP_IMAGE_MODELS, t as BUILTIN_CLIP_MODELS, tn as PoolMemoryWatchdog } from "../clip-model-registry-
|
|
1
|
+
import { Qi as BaseAddon, _r as embeddingRequirementsUnavailableError, a as CLIP_EMBEDDING_STEP_ID, da as normalizeNodeId, gi as resolvePoolMemoryPolicy, gr as embeddingRequirementsFailedError, i as CLIP_TEXT_MODELS, l as resolveClusterModelPin, mr as embeddingModelFailedError, n as DEFAULT_CLIP_MODEL, ni as parseProcStatus, pr as embeddingEncoderCapability, r as CLIP_IMAGE_MODELS, t as BUILTIN_CLIP_MODELS, tn as PoolMemoryWatchdog } from "../clip-model-registry-CoH7ORtC.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -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-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-Dj3lrvL2.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");
|
|
@@ -757,6 +757,8 @@ var DEFAULT_ACTION_TTL_MS = 15 * 6e4;
|
|
|
757
757
|
* notification that is not fresh.
|
|
758
758
|
*/
|
|
759
759
|
var SNOOZE_ACTION_TTL_MS = 60 * 6e4;
|
|
760
|
+
/** How long a Correct / Wrong button stays live: one day. */
|
|
761
|
+
var VERDICT_ACTION_TTL_MS = 1440 * 6e4;
|
|
760
762
|
/**
|
|
761
763
|
* How long a DISARM button stays live: five minutes.
|
|
762
764
|
*
|
|
@@ -784,6 +786,7 @@ function actionTtlMs(subject) {
|
|
|
784
786
|
case "sequence": return DEFAULT_ACTION_TTL_MS;
|
|
785
787
|
case "addon-update": return DEFAULT_ACTION_TTL_MS;
|
|
786
788
|
case "server-update": return DEFAULT_ACTION_TTL_MS;
|
|
789
|
+
case "detection-verdict": return VERDICT_ACTION_TTL_MS;
|
|
787
790
|
}
|
|
788
791
|
}
|
|
789
792
|
var NcActionTokenRegistry = class {
|
|
@@ -827,6 +830,30 @@ var NcActionTokenRegistry = class {
|
|
|
827
830
|
grant: found.grant
|
|
828
831
|
};
|
|
829
832
|
}
|
|
833
|
+
/**
|
|
834
|
+
* Read the grant without spending it. ONLY the confirm page of a link-only
|
|
835
|
+
* channel may call this: it exists so a link preview that opens the URL
|
|
836
|
+
* cannot spend the token, and it never runs anything.
|
|
837
|
+
*/
|
|
838
|
+
peek(id) {
|
|
839
|
+
const found = this.grants.get(id);
|
|
840
|
+
if (found === void 0) return {
|
|
841
|
+
ok: false,
|
|
842
|
+
reason: "unknown"
|
|
843
|
+
};
|
|
844
|
+
if (found.used) return {
|
|
845
|
+
ok: false,
|
|
846
|
+
reason: "already-used"
|
|
847
|
+
};
|
|
848
|
+
if (found.grant.expiresAt <= this.now()) return {
|
|
849
|
+
ok: false,
|
|
850
|
+
reason: "expired"
|
|
851
|
+
};
|
|
852
|
+
return {
|
|
853
|
+
ok: true,
|
|
854
|
+
grant: found.grant
|
|
855
|
+
};
|
|
856
|
+
}
|
|
830
857
|
/** Drop records that can no longer be claimed. Memory hygiene only — an
|
|
831
858
|
* expired record is already refused by {@link claim}, so a sweep that never
|
|
832
859
|
* ran would cost bytes, never safety. */
|
|
@@ -868,6 +895,7 @@ function describeGrant(grant) {
|
|
|
868
895
|
return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
|
|
869
896
|
case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
|
|
870
897
|
case "server-update": return `Server update on ${grant.nodeId}`;
|
|
898
|
+
case "detection-verdict": return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
|
|
871
899
|
}
|
|
872
900
|
}
|
|
873
901
|
/** The audit line's `meta`, per family. A snooze that logged a `sequence` it
|
|
@@ -921,6 +949,15 @@ function grantMeta(grant) {
|
|
|
921
949
|
...common,
|
|
922
950
|
nodeId: grant.nodeId
|
|
923
951
|
};
|
|
952
|
+
case "detection-verdict": return {
|
|
953
|
+
...common,
|
|
954
|
+
ruleId: grant.ruleId,
|
|
955
|
+
rule: grant.ruleName,
|
|
956
|
+
userId: grant.userId,
|
|
957
|
+
trackId: grant.trackId,
|
|
958
|
+
verdict: grant.verdict,
|
|
959
|
+
suggestedClass: grant.suggestedClass ?? null
|
|
960
|
+
};
|
|
924
961
|
}
|
|
925
962
|
}
|
|
926
963
|
/** What the human standing in front of the page is told. */
|
|
@@ -929,6 +966,11 @@ var FAILURE_COPY = {
|
|
|
929
966
|
expired: "This button has expired. Open CamStack to do it there.",
|
|
930
967
|
"already-used": "Already done — this button had been used."
|
|
931
968
|
};
|
|
969
|
+
/** The question a link-only channel lands on, answered by a POST form. */
|
|
970
|
+
function confirmPage(url, grant) {
|
|
971
|
+
const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&confirm=1`;
|
|
972
|
+
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
|
|
973
|
+
}
|
|
932
974
|
function page(title, detail) {
|
|
933
975
|
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style><main><h1>${title}</h1><p>${detail}</p></main>`;
|
|
934
976
|
}
|
|
@@ -942,7 +984,8 @@ var NcActionPlane = class {
|
|
|
942
984
|
handler = async (req, res) => {
|
|
943
985
|
const url = new URL(req.url ?? "/", "http://placeholder");
|
|
944
986
|
const id = decodeURIComponent(url.pathname.split("/").filter(Boolean).pop() ?? "");
|
|
945
|
-
const
|
|
987
|
+
const posted = (req.method ?? "GET").toUpperCase() === "POST";
|
|
988
|
+
const background = posted && url.searchParams.get("confirm") !== "1";
|
|
946
989
|
if (!verifyActionSignature({
|
|
947
990
|
secret: this.deps.secret,
|
|
948
991
|
id,
|
|
@@ -954,6 +997,13 @@ var NcActionPlane = class {
|
|
|
954
997
|
this.reply(res, background, 404, "Not found", "This link is not valid.");
|
|
955
998
|
return;
|
|
956
999
|
}
|
|
1000
|
+
if (!posted) {
|
|
1001
|
+
const peeked = this.deps.registry.peek(id);
|
|
1002
|
+
if (peeked.ok && peeked.grant.kind === "detection-verdict" && peeked.grant.viaLink === true) {
|
|
1003
|
+
this.sendBody(res, 200, "text/html; charset=utf-8", confirmPage(url, peeked.grant));
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
957
1007
|
const claim = this.deps.registry.claim(id);
|
|
958
1008
|
if (!claim.ok) {
|
|
959
1009
|
this.deps.logger.info("nc action callback not claimable", { meta: {
|
|
@@ -1099,6 +1149,7 @@ var ncDurableRecordKinds = require_clip_model_registry.defineRecordKinds({
|
|
|
1099
1149
|
"state-summary-stamps": ledger("digest delivery stamps per rule kind"),
|
|
1100
1150
|
meta: ledger("outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
|
|
1101
1151
|
snoozes: ledger("operator opens or closes a snooze window (write-through)"),
|
|
1152
|
+
"detection-verdicts": ledger("operator taps Correct or Wrong on a detection notification; one row per track"),
|
|
1102
1153
|
"snooze-suppressed": {
|
|
1103
1154
|
...QUERIED,
|
|
1104
1155
|
class: "ledger",
|
|
@@ -9234,6 +9285,9 @@ var en_default = {
|
|
|
9234
9285
|
"action.addon-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
|
|
9235
9286
|
"action.addon-update.unknown": "Could not read what needs updating on {{nodeId}} — nothing was changed. Open CamStack.",
|
|
9236
9287
|
"button.server-update.apply": "Update server on {{nodeId}}",
|
|
9288
|
+
"button.verdict.correct": "Correct",
|
|
9289
|
+
"button.verdict.wrong": "Wrong",
|
|
9290
|
+
"button.verdict.wrongAs": "Wrong: it is {{class}}",
|
|
9237
9291
|
"action.server-update.title": "Server update",
|
|
9238
9292
|
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
9239
9293
|
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
@@ -9635,6 +9689,9 @@ var it_default = {
|
|
|
9635
9689
|
"action.addon-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
|
|
9636
9690
|
"action.addon-update.unknown": "Non è stato possibile leggere cosa aggiornare su {{nodeId}} — non è stato cambiato nulla. Apri CamStack.",
|
|
9637
9691
|
"button.server-update.apply": "Aggiorna server su {{nodeId}}",
|
|
9692
|
+
"button.verdict.correct": "Corretto",
|
|
9693
|
+
"button.verdict.wrong": "Sbagliato",
|
|
9694
|
+
"button.verdict.wrongAs": "Sbagliato: è {{class}}",
|
|
9638
9695
|
"action.server-update.title": "Aggiornamento server",
|
|
9639
9696
|
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
9640
9697
|
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
@@ -59797,6 +59854,25 @@ var TrackAlreadyTrainedError = class extends Error {
|
|
|
59797
59854
|
}
|
|
59798
59855
|
};
|
|
59799
59856
|
/**
|
|
59857
|
+
* Classify a failed `setTrackFlags` as one of the lifecycle's DELIBERATE
|
|
59858
|
+
* refusals, or `null` when it is a failure of the call itself.
|
|
59859
|
+
*
|
|
59860
|
+
* Needed because the caller that matters (a notification button) reaches the
|
|
59861
|
+
* method over `ctx.api`, where the error class does not survive the hop and
|
|
59862
|
+
* only the message does. The class check comes first; the message check is the
|
|
59863
|
+
* transported form, and `track-flags.spec.ts` pins it against the real
|
|
59864
|
+
* messages so a reworded refusal cannot silently turn into a generic failure.
|
|
59865
|
+
*/
|
|
59866
|
+
function retrainRefusalReason(err) {
|
|
59867
|
+
if (err instanceof TrackAlreadyTrainedError) return "trained";
|
|
59868
|
+
if (err instanceof StagingBudgetExceededError) return "budget";
|
|
59869
|
+
const message = err instanceof Error ? err.message : "";
|
|
59870
|
+
if (message.includes("is already trained")) return "trained";
|
|
59871
|
+
if (message.includes("in retrain staging")) return "budget";
|
|
59872
|
+
if (message.includes("unknown track")) return "unknown-track";
|
|
59873
|
+
return null;
|
|
59874
|
+
}
|
|
59875
|
+
/**
|
|
59800
59876
|
* The `debugNote` half of the patch — `{}` when nothing should be written.
|
|
59801
59877
|
*
|
|
59802
59878
|
* The note is not an independent column: it is the operator's sentence about
|
|
@@ -81938,6 +82014,10 @@ var NC_DEVICE_ROW_KINDS = [
|
|
|
81938
82014
|
{
|
|
81939
82015
|
key: "nc-artifacts",
|
|
81940
82016
|
kind: ncDurableRecordKinds.artifacts
|
|
82017
|
+
},
|
|
82018
|
+
{
|
|
82019
|
+
key: "nc-detection-verdicts",
|
|
82020
|
+
kind: ncDurableRecordKinds["detection-verdicts"]
|
|
81941
82021
|
}
|
|
81942
82022
|
];
|
|
81943
82023
|
/**
|
|
@@ -82357,6 +82437,82 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82357
82437
|
}
|
|
82358
82438
|
};
|
|
82359
82439
|
//#endregion
|
|
82440
|
+
//#region src/notification-center/detection-verdict-ledger.ts
|
|
82441
|
+
/**
|
|
82442
|
+
* @durable class=ledger owner=notification-center
|
|
82443
|
+
* write="the operator taps Correct / Wrong on a detection notification; one row per track, the latest answer replaces the earlier one"
|
|
82444
|
+
* retention="none — bounded by the tracks a rule asked about; deleted with the camera by the device-removal hook"
|
|
82445
|
+
*/
|
|
82446
|
+
var NC_DETECTION_VERDICTS_COLLECTION = "notification-center:detection-verdicts";
|
|
82447
|
+
var NC_DETECTION_VERDICTS_COLUMNS = [
|
|
82448
|
+
{
|
|
82449
|
+
name: "id",
|
|
82450
|
+
type: "TEXT",
|
|
82451
|
+
primaryKey: true,
|
|
82452
|
+
notNull: true
|
|
82453
|
+
},
|
|
82454
|
+
{
|
|
82455
|
+
name: "ruleId",
|
|
82456
|
+
type: "TEXT",
|
|
82457
|
+
notNull: true
|
|
82458
|
+
},
|
|
82459
|
+
{
|
|
82460
|
+
name: "at",
|
|
82461
|
+
type: "INTEGER",
|
|
82462
|
+
notNull: true
|
|
82463
|
+
},
|
|
82464
|
+
(
|
|
82465
|
+
/** The FULL row (Zod-validated on read). */
|
|
82466
|
+
{
|
|
82467
|
+
name: "row",
|
|
82468
|
+
type: "JSON",
|
|
82469
|
+
notNull: true
|
|
82470
|
+
})
|
|
82471
|
+
];
|
|
82472
|
+
var NcDetectionVerdictRecordSchema = require_clip_model_registry.object({
|
|
82473
|
+
trackId: require_clip_model_registry.string().min(1),
|
|
82474
|
+
deviceId: require_clip_model_registry.number().int(),
|
|
82475
|
+
verdict: require_clip_model_registry._enum(["correct", "wrong"]),
|
|
82476
|
+
/** The class the operator said it really was. Only with `wrong`. */
|
|
82477
|
+
suggestedClass: require_clip_model_registry.string().min(1).optional(),
|
|
82478
|
+
/** Whose notification it was (the token's frozen owner), NOT who tapped. */
|
|
82479
|
+
userId: require_clip_model_registry.string().min(1),
|
|
82480
|
+
ruleId: require_clip_model_registry.string().min(1),
|
|
82481
|
+
ruleName: require_clip_model_registry.string(),
|
|
82482
|
+
at: require_clip_model_registry.number()
|
|
82483
|
+
});
|
|
82484
|
+
var NC_DETECTION_VERDICTS_SPEC = {
|
|
82485
|
+
collection: NC_DETECTION_VERDICTS_COLLECTION,
|
|
82486
|
+
columns: [...NC_DETECTION_VERDICTS_COLUMNS],
|
|
82487
|
+
writeMode: "write-through",
|
|
82488
|
+
keyOf: (row) => row.trackId,
|
|
82489
|
+
toValue: (row) => ({
|
|
82490
|
+
ruleId: row.ruleId,
|
|
82491
|
+
at: row.at,
|
|
82492
|
+
row
|
|
82493
|
+
}),
|
|
82494
|
+
fromRecord: (_key, data) => {
|
|
82495
|
+
const parsed = NcDetectionVerdictRecordSchema.safeParse(data["row"]);
|
|
82496
|
+
return parsed.success ? parsed.data : null;
|
|
82497
|
+
},
|
|
82498
|
+
deviceIdOf: (row) => row.deviceId,
|
|
82499
|
+
loadLimit: 1e4
|
|
82500
|
+
};
|
|
82501
|
+
var NcDetectionVerdictLedger = class {
|
|
82502
|
+
ledger;
|
|
82503
|
+
constructor(deps) {
|
|
82504
|
+
this.ledger = new DurableLedger({
|
|
82505
|
+
spec: NC_DETECTION_VERDICTS_SPEC,
|
|
82506
|
+
kind: deps.records.kind(ncDurableRecordKinds["detection-verdicts"]),
|
|
82507
|
+
logger: deps.logger
|
|
82508
|
+
});
|
|
82509
|
+
}
|
|
82510
|
+
/** Write-through; throws on a failed write so the tap reports it. */
|
|
82511
|
+
async record(row) {
|
|
82512
|
+
await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
|
|
82513
|
+
}
|
|
82514
|
+
};
|
|
82515
|
+
//#endregion
|
|
82360
82516
|
//#region src/notification-center/device-prune.ts
|
|
82361
82517
|
function judgeList(list, isDead) {
|
|
82362
82518
|
const dead = list.filter(isDead);
|
|
@@ -83311,6 +83467,64 @@ var NcDeviceStateCache = class {
|
|
|
83311
83467
|
}
|
|
83312
83468
|
};
|
|
83313
83469
|
//#endregion
|
|
83470
|
+
//#region src/notification-center/detection-verdict-buttons.ts
|
|
83471
|
+
var VERDICT_CORRECT_ID = "detection-verdict-correct";
|
|
83472
|
+
var VERDICT_WRONG_ID = "detection-verdict-wrong";
|
|
83473
|
+
/** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
|
|
83474
|
+
var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
|
|
83475
|
+
/** The macro classes offered as "it was really…". Short on purpose. */
|
|
83476
|
+
var VERDICT_CLASS_CHOICES = [
|
|
83477
|
+
"person",
|
|
83478
|
+
"vehicle",
|
|
83479
|
+
"animal"
|
|
83480
|
+
];
|
|
83481
|
+
/** At most this many class buttons, and only when the channel has room. */
|
|
83482
|
+
var VERDICT_MAX_CLASS_CHOICES = 2;
|
|
83483
|
+
function verdictButtonRefusal(subject) {
|
|
83484
|
+
if (!subject.askVerdict) return "rule-off";
|
|
83485
|
+
if (subject.trackId === void 0) return "no-track";
|
|
83486
|
+
if (subject.ownerUserId === void 0) return "unowned-target";
|
|
83487
|
+
return null;
|
|
83488
|
+
}
|
|
83489
|
+
/** The classes worth offering for a subject of `className`. */
|
|
83490
|
+
function verdictClassChoices(className) {
|
|
83491
|
+
return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
|
|
83492
|
+
}
|
|
83493
|
+
/**
|
|
83494
|
+
* Which buttons a channel with `maxActions` slots gets. `maxActions` absent
|
|
83495
|
+
* (the kind catalog did not answer) is treated as the common floor of three:
|
|
83496
|
+
* the two plain choices, never the class ones. The class choices appear only
|
|
83497
|
+
* when the channel declared room for a fourth button.
|
|
83498
|
+
*/
|
|
83499
|
+
function verdictButtonSpecs(input) {
|
|
83500
|
+
const base = [{
|
|
83501
|
+
actionId: VERDICT_CORRECT_ID,
|
|
83502
|
+
verdict: "correct"
|
|
83503
|
+
}, {
|
|
83504
|
+
actionId: VERDICT_WRONG_ID,
|
|
83505
|
+
verdict: "wrong"
|
|
83506
|
+
}];
|
|
83507
|
+
if (input.maxActions === void 0 || input.maxActions <= 3) return base;
|
|
83508
|
+
const room = input.maxActions - base.length;
|
|
83509
|
+
const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
|
|
83510
|
+
actionId: `${VERDICT_WRONG_AS_PREFIX}${suggestedClass}`,
|
|
83511
|
+
verdict: "wrong",
|
|
83512
|
+
suggestedClass
|
|
83513
|
+
}));
|
|
83514
|
+
return [...base, ...extra];
|
|
83515
|
+
}
|
|
83516
|
+
/** Is this one of our buttons (so a link-only channel can lift it into the body)? */
|
|
83517
|
+
function isVerdictAction(action) {
|
|
83518
|
+
return action.id === VERDICT_CORRECT_ID || action.id.startsWith(VERDICT_WRONG_ID);
|
|
83519
|
+
}
|
|
83520
|
+
/**
|
|
83521
|
+
* The body lines that carry the verdict choices on a channel with no buttons.
|
|
83522
|
+
* A bare URL per line: every chat and mail client linkifies it.
|
|
83523
|
+
*/
|
|
83524
|
+
function verdictLinkLines(actions) {
|
|
83525
|
+
return actions.filter(isVerdictAction).map((a) => `${a.label}: ${a.url ?? ""}`).join("\n");
|
|
83526
|
+
}
|
|
83527
|
+
//#endregion
|
|
83314
83528
|
//#region src/notification-center/state-summary/state-model.ts
|
|
83315
83529
|
/**
|
|
83316
83530
|
* The STATE SUMMARY's vocabulary (D647): its sections, and what one reported
|
|
@@ -87019,6 +87233,8 @@ var NcDispatcher = class {
|
|
|
87019
87233
|
targetId: target.id,
|
|
87020
87234
|
...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
|
|
87021
87235
|
...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
|
|
87236
|
+
...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
|
|
87237
|
+
...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
|
|
87022
87238
|
...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
|
|
87023
87239
|
});
|
|
87024
87240
|
} catch (err) {
|
|
@@ -87114,9 +87330,13 @@ var NcDispatcher = class {
|
|
|
87114
87330
|
...summarySectionOf(systemEvent.data)
|
|
87115
87331
|
} } : {}
|
|
87116
87332
|
}) : null;
|
|
87117
|
-
const
|
|
87333
|
+
const minted = await this.mintActions(entry, target);
|
|
87334
|
+
const linkOnly = target.maxActions === 0;
|
|
87335
|
+
const verdictLinks = linkOnly ? verdictLinkLines(minted) : "";
|
|
87336
|
+
const actions = linkOnly ? minted.filter((a) => !isVerdictAction(a)) : minted;
|
|
87337
|
+
const withLinks = (text) => verdictLinks === "" ? text : `${text}\n\n${verdictLinks}`;
|
|
87118
87338
|
const notification = {
|
|
87119
|
-
body,
|
|
87339
|
+
body: withLinks(body),
|
|
87120
87340
|
title,
|
|
87121
87341
|
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
87122
87342
|
...params,
|
|
@@ -87138,7 +87358,7 @@ var NcDispatcher = class {
|
|
|
87138
87358
|
grouped: resolved.grouped,
|
|
87139
87359
|
withAiText: (aiText) => ({
|
|
87140
87360
|
...notification,
|
|
87141
|
-
body: renderBody(aiText)
|
|
87361
|
+
body: withLinks(renderBody(aiText))
|
|
87142
87362
|
})
|
|
87143
87363
|
};
|
|
87144
87364
|
}
|
|
@@ -99751,6 +99971,55 @@ async function mintButtons(host, input) {
|
|
|
99751
99971
|
const announcement = stored === null && input.ruleId === NC_ALARM_ANNOUNCE_RULE_ID;
|
|
99752
99972
|
if (stored === null && !announcement) return [];
|
|
99753
99973
|
const drafts = [];
|
|
99974
|
+
const verdictRefusal = verdictButtonRefusal({
|
|
99975
|
+
askVerdict: stored?.askVerdict === true,
|
|
99976
|
+
...input.trackId !== void 0 ? { trackId: input.trackId } : {},
|
|
99977
|
+
...input.ownerUserId !== void 0 ? { ownerUserId: input.ownerUserId } : {}
|
|
99978
|
+
});
|
|
99979
|
+
if (stored?.askVerdict === true && verdictRefusal !== null) host.logger.info("detection notification carries no verdict buttons", {
|
|
99980
|
+
tags: { deviceId: input.deviceId },
|
|
99981
|
+
meta: {
|
|
99982
|
+
ruleId: input.ruleId,
|
|
99983
|
+
reason: verdictRefusal,
|
|
99984
|
+
targetId: input.targetId
|
|
99985
|
+
}
|
|
99986
|
+
});
|
|
99987
|
+
const verdictTrackId = input.trackId;
|
|
99988
|
+
const verdictOwner = input.ownerUserId;
|
|
99989
|
+
const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
|
|
99990
|
+
className: input.className,
|
|
99991
|
+
maxActions: input.maxActions
|
|
99992
|
+
}).map((spec) => {
|
|
99993
|
+
const id = (0, node_crypto.randomUUID)();
|
|
99994
|
+
drafts.push({
|
|
99995
|
+
id,
|
|
99996
|
+
ttlMs: actionTtlMs({ kind: "detection-verdict" }),
|
|
99997
|
+
grant: (expiresAt) => ({
|
|
99998
|
+
kind: "detection-verdict",
|
|
99999
|
+
id,
|
|
100000
|
+
verdict: spec.verdict,
|
|
100001
|
+
trackId: verdictTrackId,
|
|
100002
|
+
ruleId: input.ruleId,
|
|
100003
|
+
ruleName: input.ruleName,
|
|
100004
|
+
userId: verdictOwner,
|
|
100005
|
+
...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
|
|
100006
|
+
...input.maxActions === 0 ? { viaLink: true } : {},
|
|
100007
|
+
deviceId: input.deviceId,
|
|
100008
|
+
actionId: spec.actionId,
|
|
100009
|
+
targetId: input.targetId,
|
|
100010
|
+
expiresAt
|
|
100011
|
+
})
|
|
100012
|
+
});
|
|
100013
|
+
return {
|
|
100014
|
+
id: spec.actionId,
|
|
100015
|
+
label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict === "correct" ? "button.verdict.correct" : "button.verdict.wrong" }) : host.texts.text({
|
|
100016
|
+
key: "button.verdict.wrongAs",
|
|
100017
|
+
vars: { class: spec.suggestedClass }
|
|
100018
|
+
}),
|
|
100019
|
+
url: id,
|
|
100020
|
+
mode: "background"
|
|
100021
|
+
};
|
|
100022
|
+
});
|
|
99754
100023
|
const sequenceButtons = host.actionRunner === null || stored === null ? [] : buildActionButtons({
|
|
99755
100024
|
actions: stored.actions,
|
|
99756
100025
|
knownSequenceIds: host.sequences.knownIds(),
|
|
@@ -99963,6 +100232,7 @@ async function mintButtons(host, input) {
|
|
|
99963
100232
|
}
|
|
99964
100233
|
});
|
|
99965
100234
|
const out = [
|
|
100235
|
+
...verdictButtons,
|
|
99966
100236
|
...sequenceButtons,
|
|
99967
100237
|
...snoozeButtons,
|
|
99968
100238
|
...alarmButtons,
|
|
@@ -100186,8 +100456,53 @@ async function runGrantedAction(host, grant) {
|
|
|
100186
100456
|
case "sequence": return host.runGrantedSequence(grant);
|
|
100187
100457
|
case "addon-update": return host.applyGrantedAddonUpdate(grant);
|
|
100188
100458
|
case "server-update": return host.applyGrantedServerUpdate(grant);
|
|
100459
|
+
case "detection-verdict": return host.applyGrantedDetectionVerdict(grant);
|
|
100189
100460
|
}
|
|
100190
100461
|
}
|
|
100462
|
+
/** What a person is told when the retrain lifecycle refuses the mark. */
|
|
100463
|
+
var VERDICT_REFUSAL_COPY = {
|
|
100464
|
+
trained: "This track was already used for training — nothing was changed",
|
|
100465
|
+
budget: "This camera already holds as many tracks for retraining as it may — nothing was changed",
|
|
100466
|
+
"unknown-track": "This track is no longer available — nothing was changed"
|
|
100467
|
+
};
|
|
100468
|
+
/**
|
|
100469
|
+
* Mark the track one notification reported for retraining, and record who was
|
|
100470
|
+
* asked and when.
|
|
100471
|
+
*
|
|
100472
|
+
* Order matters. The MARK goes first and is the authority: a refusal ("already
|
|
100473
|
+
* trained", "budget full") means the operator's answer changed nothing, so no
|
|
100474
|
+
* ledger row is written for an answer that was not applied. The token is
|
|
100475
|
+
* already spent (D47) — the refusal is shown as the reason, not a retry.
|
|
100476
|
+
*/
|
|
100477
|
+
async function applyGrantedDetectionVerdict(host, grant) {
|
|
100478
|
+
const mark = host.deps.markTrackForRetrain;
|
|
100479
|
+
if (mark === void 0) throw new Error("no retrain mark is wired on this node");
|
|
100480
|
+
const outcome = await mark({
|
|
100481
|
+
deviceId: grant.deviceId,
|
|
100482
|
+
trackId: grant.trackId
|
|
100483
|
+
});
|
|
100484
|
+
if (!outcome.ok) {
|
|
100485
|
+
host.logger.info("detection verdict changed nothing — the retrain flow refused the mark", {
|
|
100486
|
+
tags: { deviceId: grant.deviceId },
|
|
100487
|
+
meta: {
|
|
100488
|
+
trackId: grant.trackId,
|
|
100489
|
+
reason: outcome.reason,
|
|
100490
|
+
verdict: grant.verdict
|
|
100491
|
+
}
|
|
100492
|
+
});
|
|
100493
|
+
throw new NcActionRefusedError(VERDICT_REFUSAL_COPY[outcome.reason]);
|
|
100494
|
+
}
|
|
100495
|
+
await host.verdicts.record({
|
|
100496
|
+
trackId: grant.trackId,
|
|
100497
|
+
deviceId: grant.deviceId,
|
|
100498
|
+
verdict: grant.verdict,
|
|
100499
|
+
...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
|
|
100500
|
+
userId: grant.userId,
|
|
100501
|
+
ruleId: grant.ruleId,
|
|
100502
|
+
ruleName: grant.ruleName,
|
|
100503
|
+
at: host.now()
|
|
100504
|
+
});
|
|
100505
|
+
}
|
|
100191
100506
|
/**
|
|
100192
100507
|
* Apply the addon updates one notification was about.
|
|
100193
100508
|
*
|
|
@@ -105364,6 +105679,7 @@ var NotificationCenter = class {
|
|
|
105364
105679
|
* evaluating one.
|
|
105365
105680
|
*/
|
|
105366
105681
|
pruneLedger;
|
|
105682
|
+
verdicts;
|
|
105367
105683
|
/** D905: which rules cannot work, announced once per distinct reason. */
|
|
105368
105684
|
ruleHealth = new NcRuleHealthMonitor();
|
|
105369
105685
|
/** The prune itself — see `device-prune-runner.ts`. */
|
|
@@ -105592,6 +105908,11 @@ var NotificationCenter = class {
|
|
|
105592
105908
|
applyGrantedArmAnyway: (panel, grant) => self.applyGrantedArmAnyway(panel, grant),
|
|
105593
105909
|
applyGrantedBypassInput: (panel, grant) => self.applyGrantedBypassInput(panel, grant),
|
|
105594
105910
|
applyGrantedServerUpdate: (grant) => self.applyGrantedServerUpdate(grant),
|
|
105911
|
+
applyGrantedDetectionVerdict: (grant) => self.applyGrantedDetectionVerdict(grant),
|
|
105912
|
+
now: self.now,
|
|
105913
|
+
get verdicts() {
|
|
105914
|
+
return self.verdicts;
|
|
105915
|
+
},
|
|
105595
105916
|
applyGrantedSnooze: (grant) => self.applyGrantedSnooze(grant),
|
|
105596
105917
|
get deps() {
|
|
105597
105918
|
return self.deps;
|
|
@@ -105642,6 +105963,9 @@ var NotificationCenter = class {
|
|
|
105642
105963
|
async applyGrantedAddonUpdate(grant) {
|
|
105643
105964
|
return applyGrantedAddonUpdate(this.grantedActionsHost(), grant);
|
|
105644
105965
|
}
|
|
105966
|
+
async applyGrantedDetectionVerdict(grant) {
|
|
105967
|
+
return applyGrantedDetectionVerdict(this.grantedActionsHost(), grant);
|
|
105968
|
+
}
|
|
105645
105969
|
async applyGrantedServerUpdate(grant) {
|
|
105646
105970
|
return applyGrantedServerUpdate(this.grantedActionsHost(), grant);
|
|
105647
105971
|
}
|
|
@@ -105881,6 +106205,10 @@ var NotificationCenter = class {
|
|
|
105881
106205
|
}
|
|
105882
106206
|
this.summaryRules = NcSummaryStore.of(deps.records, this.logger.child("summary"), deps.now);
|
|
105883
106207
|
this.calendarEventsRules = NcCalendarEventsStore.of(deps.records, this.logger.child("calendar-events"), deps.now);
|
|
106208
|
+
this.verdicts = new NcDetectionVerdictLedger({
|
|
106209
|
+
...durableStore,
|
|
106210
|
+
logger: this.logger.child("detection-verdicts")
|
|
106211
|
+
});
|
|
105884
106212
|
this.pruneLedger = new NcPruneLedger({
|
|
105885
106213
|
...durableStore,
|
|
105886
106214
|
logger: this.logger.child("device-prune")
|
|
@@ -108488,6 +108816,23 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108488
108816
|
};
|
|
108489
108817
|
}
|
|
108490
108818
|
},
|
|
108819
|
+
markTrackForRetrain: async ({ deviceId, trackId }) => {
|
|
108820
|
+
try {
|
|
108821
|
+
await this.ctx.api.pipelineAnalytics.setTrackFlags.mutate({
|
|
108822
|
+
deviceId,
|
|
108823
|
+
trackId,
|
|
108824
|
+
flags: { markForTrain: true }
|
|
108825
|
+
});
|
|
108826
|
+
return { ok: true };
|
|
108827
|
+
} catch (err) {
|
|
108828
|
+
const reason = retrainRefusalReason(err);
|
|
108829
|
+
if (reason === null) throw err;
|
|
108830
|
+
return {
|
|
108831
|
+
ok: false,
|
|
108832
|
+
reason
|
|
108833
|
+
};
|
|
108834
|
+
}
|
|
108835
|
+
},
|
|
108491
108836
|
readDeviceStates: async (ids) => {
|
|
108492
108837
|
const out = /* @__PURE__ */ new Map();
|
|
108493
108838
|
for (const id of ids) try {
|
|
@@ -108559,9 +108904,11 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108559
108904
|
const targets = await api.notificationOutput.listTargets.query({});
|
|
108560
108905
|
const kinds = await api.notificationOutput.listTargetKinds.query({}).catch(() => []);
|
|
108561
108906
|
const bodyMaxLenByKind = new Map(kinds.map((k) => [k.kind, k.caps.bodyMaxLen]));
|
|
108907
|
+
const maxActionsByKind = new Map(kinds.map((k) => [k.kind, k.caps.actions]));
|
|
108562
108908
|
return targets.map((t) => {
|
|
108563
108909
|
const owner = t.config["ownerUserId"];
|
|
108564
108910
|
const bodyMaxLen = bodyMaxLenByKind.get(t.kind);
|
|
108911
|
+
const maxActions = maxActionsByKind.get(t.kind);
|
|
108565
108912
|
return {
|
|
108566
108913
|
id: t.id,
|
|
108567
108914
|
addonId: t.addonId,
|
|
@@ -108569,7 +108916,8 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108569
108916
|
kind: t.kind,
|
|
108570
108917
|
enabled: t.enabled,
|
|
108571
108918
|
...typeof owner === "string" && owner.length > 0 ? { ownerUserId: owner } : {},
|
|
108572
|
-
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {}
|
|
108919
|
+
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {},
|
|
108920
|
+
...typeof maxActions === "number" ? { maxActions } : {}
|
|
108573
108921
|
};
|
|
108574
108922
|
});
|
|
108575
108923
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as MediaFileKindEnum, $i as CAP_ADVANCE_FIELDS, $n as cloneAlarmSettings, $r as paintHex, $t as OpsLogEntrySchema, A as DeclaredDevices, Aa as EventCategory, Ai as subKindsOf, An as TrackSideSchema, Ar as isAudioClassName, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as templateCarriesSummary, Bn as applyModeDelaysEdits, Br as isRootSuppliableStepId, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as record, Ci as ruleKindOf, Cn as TimelapseRuleInputSchema, Cr as failureContributionCapability, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Da as tuple, Di as servedTemplateVars, Dn as TrackPuritySchema, Dr as isAlertEpisodeOpen, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as templateLiteral, Ei as sensorRaisedOf, En as TrackPlacementSchema, Er as isAlarmControllableCap, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventDeliveryProblems, Fn as alarmPanelCapability, Fr as isEventOwnerType, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as zoneAnalyticsCapability, Gn as audioMetricsCapability, Gr as kebabToCamel, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as vectorDimFromBase64, Hn as applySecureOnArmEdit, Hr as isSystemDelivery, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventFamilyProblems, In as alertEpisodeId, Ir as isKnownTemplateVar, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as evaluateSensorEdge, Jn as buildSubjectPath, Jr as memoizePinnedFfmpeg, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Kn as audioModeOf, Kr as linkedStepOf, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as systemEventFilterApplies, Ln as alertEpisodeKind, Lr as isLabelSupplyStepId, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as subjectPathSvgPoints, Mn as activateAlarmProfile, Mr as isDetectionMacroClass, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as summarizeReview, Nn as addonWidgetsSourceCapability, Nr as isDrawableSubjectPath, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oa as union, Oi as startMoverUnderClearance, On as TrackRelationKindSchema, Or as isAlertEpisodeSeverity, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as syncActiveAlarmProfileSettings, Pn as alarmDeviceSecureOnArm, Pr as isEventKindLabelKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as BaseAddon, Qn as classifyRetrainReview, Qr as occupancyScope, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as systemEventSummaryScheduleOf, Rn as alertEpisodePayload, Rr as isMediaOwnerType, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as partialRecord, Si as ruleGroupingApplies, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as faceGalleryCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as string, Ti as secureActuationFor, Tn as TimelapseRuleSchema, Tr as gifFromMp4Args, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as viewerLinkBaseOf, Un as armRemovalReconcile, Ur as isSystemEventSummaryRule, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as templateVarsFor, Vn as applyNonBlockingEdit, Vr as isScheduleActive, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as withSystemEventClasses, Wn as assertTimelapseCadences, Wr as isVectorIndexDimConflict, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as DeviceType, Xn as bundledCamstack, Xr as normalizeNcMediaPolicy, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as isSourceCap, Yn as buildViewerLink, Yr as ncMediaPolicyError, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as errMsg, Zn as calendarEventLocalSpan, Zr as notificationRulesCapability, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as discriminatedUnion, _i as retrainRevisionConflictMessage, _n as SceneMonitorSchema, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as customAction, ai as plateGalleryCapability, an as RetrainDismissedSchema, ar as deleteAlarmProfile, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as number, bi as ruleBelongsToAlarm$1, bn as SetTrackRelationInputSchema, br as eventKindLabel, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as hydrateSchema, ci as readMotionTriggeredBy, cn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, cr as deriveRecordingMode, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, di as resolveClipSearchMinScore, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceAttentionKind, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as CamProfileSchema, ei as paletteResultSchema, en as PackageLlmVerdictSchema, er as consumableFormOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as sleep$1, fi as resolveLocationMode, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, fr as deviceRemovalHookCapability, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as boolean, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as array, hi as resolveNcMediaImage, hn as SCENE_DIVERGED, hr as embeddingRefusalOf, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as createEvent, ii as pipelineAnalyticsCapability, in as RebuildObjectEmbeddingsRefusalSchema, ir as createAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as subjectPathSampleFromBox, jn as TrackSourceSchema, jr as isDefaultAlarmProfileId, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ka as unknown, ki as storageOccupancyCapability, kn as TrackRelationSchema, kr as isAppLinkBase, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as isDeviceScopedCap, li as readTimelapseGeneratedAt, ln as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, lr as describeSystemEventDeliveryProblem, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as _enum, mi as resolveModeNotifyPolicy, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as RECORD_MAX_DATA_BYTES, nn as RECORDING_EXPORT_MAX_READ_BYTES, nr as coreBlockIdFromAddonId, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as defineCustomActions, oi as projectSubjectPath, on as RetrainOriginSchema, or as deriveDetailCropRect, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as ZodObject, pi as resolveModeDelays, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as canonicalHash, qn as buildEventKindDescriptor, qr as mayWriteToLocation, qt as NcSystemEventKindSchema, ra as RECORD_QUERY_MAX, ri as pickFaceRescoreIntervalMinutes, rn as RECORDING_SOURCE_CAMSTACK_ADDON, rr as cosineSimilarity$1, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as defineRecordKinds, si as readDeviceStateFrom, sn as RetrainStatusSchema, sr as derivePostTriggeredAction, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as InvalidRecordError, ti as parseAlertEpisodePayload, tr as consumableLowPercentOf, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as nodePin, ui as renameAlarmProfile, un as SCENE_DEFAULT_ANCHOR_THRESHOLD, ur as describeSystemEventFamilyProblem, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, va as literal, vi as rootSupplyServes, vn as SetTrackPurityInputSchema, vr as encodeVectorBase64, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as strictObject, wi as sceneMonitorCapability, wn as TimelapseRulePatchSchema, wr as gifDeriveSpeed, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as object, xi as ruleDeviceCompatCapQueries, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as evictionPolicyOfLocation, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as looseObject, yi as ruleAutoTriggersAlarm, yn as SetTrackPurityResultSchema, yr as ensureAlarmProfiles, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as systemEventSummarySplitOf, zn as applyBlockingEdit, zr as isRetiredClearKey, zt as NcRuleTargetSchema } from "../clip-model-registry-
|
|
1
|
+
import { $ as MediaFileKindEnum, $i as CAP_ADVANCE_FIELDS, $n as cloneAlarmSettings, $r as paintHex, $t as OpsLogEntrySchema, A as DeclaredDevices, Aa as EventCategory, Ai as subKindsOf, An as TrackSideSchema, Ar as isAudioClassName, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as templateCarriesSummary, Bn as applyModeDelaysEdits, Br as isRootSuppliableStepId, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as record, Ci as ruleKindOf, Cn as TimelapseRuleInputSchema, Cr as failureContributionCapability, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Da as tuple, Di as servedTemplateVars, Dn as TrackPuritySchema, Dr as isAlertEpisodeOpen, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as templateLiteral, Ei as sensorRaisedOf, En as TrackPlacementSchema, Er as isAlarmControllableCap, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventDeliveryProblems, Fn as alarmPanelCapability, Fr as isEventOwnerType, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as zoneAnalyticsCapability, Gn as audioMetricsCapability, Gr as kebabToCamel, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as vectorDimFromBase64, Hn as applySecureOnArmEdit, Hr as isSystemDelivery, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventFamilyProblems, In as alertEpisodeId, Ir as isKnownTemplateVar, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as evaluateSensorEdge, Jn as buildSubjectPath, Jr as memoizePinnedFfmpeg, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Kn as audioModeOf, Kr as linkedStepOf, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as systemEventFilterApplies, Ln as alertEpisodeKind, Lr as isLabelSupplyStepId, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as subjectPathSvgPoints, Mn as activateAlarmProfile, Mr as isDetectionMacroClass, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as summarizeReview, Nn as addonWidgetsSourceCapability, Nr as isDrawableSubjectPath, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oa as union, Oi as startMoverUnderClearance, On as TrackRelationKindSchema, Or as isAlertEpisodeSeverity, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as syncActiveAlarmProfileSettings, Pn as alarmDeviceSecureOnArm, Pr as isEventKindLabelKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as BaseAddon, Qn as classifyRetrainReview, Qr as occupancyScope, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as systemEventSummaryScheduleOf, Rn as alertEpisodePayload, Rr as isMediaOwnerType, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as partialRecord, Si as ruleGroupingApplies, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as faceGalleryCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as string, Ti as secureActuationFor, Tn as TimelapseRuleSchema, Tr as gifFromMp4Args, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as viewerLinkBaseOf, Un as armRemovalReconcile, Ur as isSystemEventSummaryRule, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as templateVarsFor, Vn as applyNonBlockingEdit, Vr as isScheduleActive, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as withSystemEventClasses, Wn as assertTimelapseCadences, Wr as isVectorIndexDimConflict, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as DeviceType, Xn as bundledCamstack, Xr as normalizeNcMediaPolicy, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as isSourceCap, Yn as buildViewerLink, Yr as ncMediaPolicyError, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as errMsg, Zn as calendarEventLocalSpan, Zr as notificationRulesCapability, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as discriminatedUnion, _i as retrainRevisionConflictMessage, _n as SceneMonitorSchema, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as customAction, ai as plateGalleryCapability, an as RetrainDismissedSchema, ar as deleteAlarmProfile, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as number, bi as ruleBelongsToAlarm$1, bn as SetTrackRelationInputSchema, br as eventKindLabel, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as hydrateSchema, ci as readMotionTriggeredBy, cn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, cr as deriveRecordingMode, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, di as resolveClipSearchMinScore, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceAttentionKind, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as CamProfileSchema, ei as paletteResultSchema, en as PackageLlmVerdictSchema, er as consumableFormOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as sleep$1, fi as resolveLocationMode, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, fr as deviceRemovalHookCapability, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as boolean, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as array, hi as resolveNcMediaImage, hn as SCENE_DIVERGED, hr as embeddingRefusalOf, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as createEvent, ii as pipelineAnalyticsCapability, in as RebuildObjectEmbeddingsRefusalSchema, ir as createAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as subjectPathSampleFromBox, jn as TrackSourceSchema, jr as isDefaultAlarmProfileId, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ka as unknown, ki as storageOccupancyCapability, kn as TrackRelationSchema, kr as isAppLinkBase, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as isDeviceScopedCap, li as readTimelapseGeneratedAt, ln as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, lr as describeSystemEventDeliveryProblem, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as _enum, mi as resolveModeNotifyPolicy, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as RECORD_MAX_DATA_BYTES, nn as RECORDING_EXPORT_MAX_READ_BYTES, nr as coreBlockIdFromAddonId, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as defineCustomActions, oi as projectSubjectPath, on as RetrainOriginSchema, or as deriveDetailCropRect, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as ZodObject, pi as resolveModeDelays, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as canonicalHash, qn as buildEventKindDescriptor, qr as mayWriteToLocation, qt as NcSystemEventKindSchema, ra as RECORD_QUERY_MAX, ri as pickFaceRescoreIntervalMinutes, rn as RECORDING_SOURCE_CAMSTACK_ADDON, rr as cosineSimilarity$1, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as defineRecordKinds, si as readDeviceStateFrom, sn as RetrainStatusSchema, sr as derivePostTriggeredAction, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as InvalidRecordError, ti as parseAlertEpisodePayload, tr as consumableLowPercentOf, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as nodePin, ui as renameAlarmProfile, un as SCENE_DEFAULT_ANCHOR_THRESHOLD, ur as describeSystemEventFamilyProblem, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, va as literal, vi as rootSupplyServes, vn as SetTrackPurityInputSchema, vr as encodeVectorBase64, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as strictObject, wi as sceneMonitorCapability, wn as TimelapseRulePatchSchema, wr as gifDeriveSpeed, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as object, xi as ruleDeviceCompatCapQueries, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as evictionPolicyOfLocation, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as looseObject, yi as ruleAutoTriggersAlarm, yn as SetTrackPurityResultSchema, yr as ensureAlarmProfiles, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as systemEventSummarySplitOf, zn as applyBlockingEdit, zr as isRetiredClearKey, zt as NcRuleTargetSchema } from "../clip-model-registry-CoH7ORtC.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -750,6 +750,8 @@ var DEFAULT_ACTION_TTL_MS = 15 * 6e4;
|
|
|
750
750
|
* notification that is not fresh.
|
|
751
751
|
*/
|
|
752
752
|
var SNOOZE_ACTION_TTL_MS = 60 * 6e4;
|
|
753
|
+
/** How long a Correct / Wrong button stays live: one day. */
|
|
754
|
+
var VERDICT_ACTION_TTL_MS = 1440 * 6e4;
|
|
753
755
|
/**
|
|
754
756
|
* How long a DISARM button stays live: five minutes.
|
|
755
757
|
*
|
|
@@ -777,6 +779,7 @@ function actionTtlMs(subject) {
|
|
|
777
779
|
case "sequence": return DEFAULT_ACTION_TTL_MS;
|
|
778
780
|
case "addon-update": return DEFAULT_ACTION_TTL_MS;
|
|
779
781
|
case "server-update": return DEFAULT_ACTION_TTL_MS;
|
|
782
|
+
case "detection-verdict": return VERDICT_ACTION_TTL_MS;
|
|
780
783
|
}
|
|
781
784
|
}
|
|
782
785
|
var NcActionTokenRegistry = class {
|
|
@@ -820,6 +823,30 @@ var NcActionTokenRegistry = class {
|
|
|
820
823
|
grant: found.grant
|
|
821
824
|
};
|
|
822
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Read the grant without spending it. ONLY the confirm page of a link-only
|
|
828
|
+
* channel may call this: it exists so a link preview that opens the URL
|
|
829
|
+
* cannot spend the token, and it never runs anything.
|
|
830
|
+
*/
|
|
831
|
+
peek(id) {
|
|
832
|
+
const found = this.grants.get(id);
|
|
833
|
+
if (found === void 0) return {
|
|
834
|
+
ok: false,
|
|
835
|
+
reason: "unknown"
|
|
836
|
+
};
|
|
837
|
+
if (found.used) return {
|
|
838
|
+
ok: false,
|
|
839
|
+
reason: "already-used"
|
|
840
|
+
};
|
|
841
|
+
if (found.grant.expiresAt <= this.now()) return {
|
|
842
|
+
ok: false,
|
|
843
|
+
reason: "expired"
|
|
844
|
+
};
|
|
845
|
+
return {
|
|
846
|
+
ok: true,
|
|
847
|
+
grant: found.grant
|
|
848
|
+
};
|
|
849
|
+
}
|
|
823
850
|
/** Drop records that can no longer be claimed. Memory hygiene only — an
|
|
824
851
|
* expired record is already refused by {@link claim}, so a sweep that never
|
|
825
852
|
* ran would cost bytes, never safety. */
|
|
@@ -861,6 +888,7 @@ function describeGrant(grant) {
|
|
|
861
888
|
return grant.action === "disarm" ? "Alarm disarmed" : `Alarm armed${grant.mode !== void 0 ? ` (${grant.mode})` : ""}`;
|
|
862
889
|
case "addon-update": return `${grant.packages.length} addon update(s) on ${grant.nodeId}`;
|
|
863
890
|
case "server-update": return `Server update on ${grant.nodeId}`;
|
|
891
|
+
case "detection-verdict": return grant.verdict === "correct" ? "Marked correct — kept for retraining" : grant.suggestedClass === void 0 ? "Marked wrong — kept for retraining" : `Marked wrong, it was a ${grant.suggestedClass} — kept for retraining`;
|
|
864
892
|
}
|
|
865
893
|
}
|
|
866
894
|
/** The audit line's `meta`, per family. A snooze that logged a `sequence` it
|
|
@@ -914,6 +942,15 @@ function grantMeta(grant) {
|
|
|
914
942
|
...common,
|
|
915
943
|
nodeId: grant.nodeId
|
|
916
944
|
};
|
|
945
|
+
case "detection-verdict": return {
|
|
946
|
+
...common,
|
|
947
|
+
ruleId: grant.ruleId,
|
|
948
|
+
rule: grant.ruleName,
|
|
949
|
+
userId: grant.userId,
|
|
950
|
+
trackId: grant.trackId,
|
|
951
|
+
verdict: grant.verdict,
|
|
952
|
+
suggestedClass: grant.suggestedClass ?? null
|
|
953
|
+
};
|
|
917
954
|
}
|
|
918
955
|
}
|
|
919
956
|
/** What the human standing in front of the page is told. */
|
|
@@ -922,6 +959,11 @@ var FAILURE_COPY = {
|
|
|
922
959
|
expired: "This button has expired. Open CamStack to do it there.",
|
|
923
960
|
"already-used": "Already done — this button had been used."
|
|
924
961
|
};
|
|
962
|
+
/** The question a link-only channel lands on, answered by a POST form. */
|
|
963
|
+
function confirmPage(url, grant) {
|
|
964
|
+
const action = `${url.pathname}?exp=${encodeURIComponent(url.searchParams.get("exp") ?? "")}&sig=${encodeURIComponent(url.searchParams.get("sig") ?? "")}&confirm=1`;
|
|
965
|
+
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 1rem}button{font:inherit;padding:.6rem 1.4rem;border-radius:.5rem;border:0;background:#3b82f6;color:#fff}</style><main><h1>Confirm</h1><p>${grant.verdict === "correct" ? "Mark this detection as correct?" : grant.suggestedClass === void 0 ? "Mark this detection as wrong?" : `Mark this detection as wrong, it was a ${grant.suggestedClass}?`}</p><form method="post" action="${action}"><button type="submit">Confirm</button></form></main>`;
|
|
966
|
+
}
|
|
925
967
|
function page(title, detail) {
|
|
926
968
|
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;min-height:100vh;display:grid;place-items:center;background:#111;color:#eee}main{max-width:24rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#aaa}</style><main><h1>${title}</h1><p>${detail}</p></main>`;
|
|
927
969
|
}
|
|
@@ -935,7 +977,8 @@ var NcActionPlane = class {
|
|
|
935
977
|
handler = async (req, res) => {
|
|
936
978
|
const url = new URL(req.url ?? "/", "http://placeholder");
|
|
937
979
|
const id = decodeURIComponent(url.pathname.split("/").filter(Boolean).pop() ?? "");
|
|
938
|
-
const
|
|
980
|
+
const posted = (req.method ?? "GET").toUpperCase() === "POST";
|
|
981
|
+
const background = posted && url.searchParams.get("confirm") !== "1";
|
|
939
982
|
if (!verifyActionSignature({
|
|
940
983
|
secret: this.deps.secret,
|
|
941
984
|
id,
|
|
@@ -947,6 +990,13 @@ var NcActionPlane = class {
|
|
|
947
990
|
this.reply(res, background, 404, "Not found", "This link is not valid.");
|
|
948
991
|
return;
|
|
949
992
|
}
|
|
993
|
+
if (!posted) {
|
|
994
|
+
const peeked = this.deps.registry.peek(id);
|
|
995
|
+
if (peeked.ok && peeked.grant.kind === "detection-verdict" && peeked.grant.viaLink === true) {
|
|
996
|
+
this.sendBody(res, 200, "text/html; charset=utf-8", confirmPage(url, peeked.grant));
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
950
1000
|
const claim = this.deps.registry.claim(id);
|
|
951
1001
|
if (!claim.ok) {
|
|
952
1002
|
this.deps.logger.info("nc action callback not claimable", { meta: {
|
|
@@ -1092,6 +1142,7 @@ var ncDurableRecordKinds = defineRecordKinds({
|
|
|
1092
1142
|
"state-summary-stamps": ledger("digest delivery stamps per rule kind"),
|
|
1093
1143
|
meta: ledger("outbox boot-reconcile watermark and other single-key meta rows", "settings", { none: "one fixed key (watermark), overwritten in place, never deleted" }),
|
|
1094
1144
|
snoozes: ledger("operator opens or closes a snooze window (write-through)"),
|
|
1145
|
+
"detection-verdicts": ledger("operator taps Correct or Wrong on a detection notification; one row per track"),
|
|
1095
1146
|
"snooze-suppressed": {
|
|
1096
1147
|
...QUERIED,
|
|
1097
1148
|
class: "ledger",
|
|
@@ -9226,6 +9277,9 @@ var en_default = {
|
|
|
9226
9277
|
"action.addon-update.uptodate": "Already up to date on {{nodeId}} — nothing to do",
|
|
9227
9278
|
"action.addon-update.unknown": "Could not read what needs updating on {{nodeId}} — nothing was changed. Open CamStack.",
|
|
9228
9279
|
"button.server-update.apply": "Update server on {{nodeId}}",
|
|
9280
|
+
"button.verdict.correct": "Correct",
|
|
9281
|
+
"button.verdict.wrong": "Wrong",
|
|
9282
|
+
"button.verdict.wrongAs": "Wrong: it is {{class}}",
|
|
9229
9283
|
"action.server-update.title": "Server update",
|
|
9230
9284
|
"action.server-update.started": "Updating server on {{nodeId}}…",
|
|
9231
9285
|
"action.server-update.restarting": "Server update staged on {{nodeId}} — the node is restarting. You will hear when it comes back.",
|
|
@@ -9627,6 +9681,9 @@ var it_default = {
|
|
|
9627
9681
|
"action.addon-update.uptodate": "Già aggiornato su {{nodeId}} — niente da fare",
|
|
9628
9682
|
"action.addon-update.unknown": "Non è stato possibile leggere cosa aggiornare su {{nodeId}} — non è stato cambiato nulla. Apri CamStack.",
|
|
9629
9683
|
"button.server-update.apply": "Aggiorna server su {{nodeId}}",
|
|
9684
|
+
"button.verdict.correct": "Corretto",
|
|
9685
|
+
"button.verdict.wrong": "Sbagliato",
|
|
9686
|
+
"button.verdict.wrongAs": "Sbagliato: è {{class}}",
|
|
9630
9687
|
"action.server-update.title": "Aggiornamento server",
|
|
9631
9688
|
"action.server-update.started": "Aggiornamento server su {{nodeId}}…",
|
|
9632
9689
|
"action.server-update.restarting": "Aggiornamento server predisposto su {{nodeId}} — il nodo si riavvia. Ti avviseremo quando torna.",
|
|
@@ -59702,6 +59759,25 @@ var TrackAlreadyTrainedError = class extends Error {
|
|
|
59702
59759
|
}
|
|
59703
59760
|
};
|
|
59704
59761
|
/**
|
|
59762
|
+
* Classify a failed `setTrackFlags` as one of the lifecycle's DELIBERATE
|
|
59763
|
+
* refusals, or `null` when it is a failure of the call itself.
|
|
59764
|
+
*
|
|
59765
|
+
* Needed because the caller that matters (a notification button) reaches the
|
|
59766
|
+
* method over `ctx.api`, where the error class does not survive the hop and
|
|
59767
|
+
* only the message does. The class check comes first; the message check is the
|
|
59768
|
+
* transported form, and `track-flags.spec.ts` pins it against the real
|
|
59769
|
+
* messages so a reworded refusal cannot silently turn into a generic failure.
|
|
59770
|
+
*/
|
|
59771
|
+
function retrainRefusalReason(err) {
|
|
59772
|
+
if (err instanceof TrackAlreadyTrainedError) return "trained";
|
|
59773
|
+
if (err instanceof StagingBudgetExceededError) return "budget";
|
|
59774
|
+
const message = err instanceof Error ? err.message : "";
|
|
59775
|
+
if (message.includes("is already trained")) return "trained";
|
|
59776
|
+
if (message.includes("in retrain staging")) return "budget";
|
|
59777
|
+
if (message.includes("unknown track")) return "unknown-track";
|
|
59778
|
+
return null;
|
|
59779
|
+
}
|
|
59780
|
+
/**
|
|
59705
59781
|
* The `debugNote` half of the patch — `{}` when nothing should be written.
|
|
59706
59782
|
*
|
|
59707
59783
|
* The note is not an independent column: it is the operator's sentence about
|
|
@@ -81843,6 +81919,10 @@ var NC_DEVICE_ROW_KINDS = [
|
|
|
81843
81919
|
{
|
|
81844
81920
|
key: "nc-artifacts",
|
|
81845
81921
|
kind: ncDurableRecordKinds.artifacts
|
|
81922
|
+
},
|
|
81923
|
+
{
|
|
81924
|
+
key: "nc-detection-verdicts",
|
|
81925
|
+
kind: ncDurableRecordKinds["detection-verdicts"]
|
|
81846
81926
|
}
|
|
81847
81927
|
];
|
|
81848
81928
|
/**
|
|
@@ -82262,6 +82342,82 @@ var NcSequenceStore = class NcSequenceStore {
|
|
|
82262
82342
|
}
|
|
82263
82343
|
};
|
|
82264
82344
|
//#endregion
|
|
82345
|
+
//#region src/notification-center/detection-verdict-ledger.ts
|
|
82346
|
+
/**
|
|
82347
|
+
* @durable class=ledger owner=notification-center
|
|
82348
|
+
* write="the operator taps Correct / Wrong on a detection notification; one row per track, the latest answer replaces the earlier one"
|
|
82349
|
+
* retention="none — bounded by the tracks a rule asked about; deleted with the camera by the device-removal hook"
|
|
82350
|
+
*/
|
|
82351
|
+
var NC_DETECTION_VERDICTS_COLLECTION = "notification-center:detection-verdicts";
|
|
82352
|
+
var NC_DETECTION_VERDICTS_COLUMNS = [
|
|
82353
|
+
{
|
|
82354
|
+
name: "id",
|
|
82355
|
+
type: "TEXT",
|
|
82356
|
+
primaryKey: true,
|
|
82357
|
+
notNull: true
|
|
82358
|
+
},
|
|
82359
|
+
{
|
|
82360
|
+
name: "ruleId",
|
|
82361
|
+
type: "TEXT",
|
|
82362
|
+
notNull: true
|
|
82363
|
+
},
|
|
82364
|
+
{
|
|
82365
|
+
name: "at",
|
|
82366
|
+
type: "INTEGER",
|
|
82367
|
+
notNull: true
|
|
82368
|
+
},
|
|
82369
|
+
(
|
|
82370
|
+
/** The FULL row (Zod-validated on read). */
|
|
82371
|
+
{
|
|
82372
|
+
name: "row",
|
|
82373
|
+
type: "JSON",
|
|
82374
|
+
notNull: true
|
|
82375
|
+
})
|
|
82376
|
+
];
|
|
82377
|
+
var NcDetectionVerdictRecordSchema = object({
|
|
82378
|
+
trackId: string().min(1),
|
|
82379
|
+
deviceId: number().int(),
|
|
82380
|
+
verdict: _enum(["correct", "wrong"]),
|
|
82381
|
+
/** The class the operator said it really was. Only with `wrong`. */
|
|
82382
|
+
suggestedClass: string().min(1).optional(),
|
|
82383
|
+
/** Whose notification it was (the token's frozen owner), NOT who tapped. */
|
|
82384
|
+
userId: string().min(1),
|
|
82385
|
+
ruleId: string().min(1),
|
|
82386
|
+
ruleName: string(),
|
|
82387
|
+
at: number()
|
|
82388
|
+
});
|
|
82389
|
+
var NC_DETECTION_VERDICTS_SPEC = {
|
|
82390
|
+
collection: NC_DETECTION_VERDICTS_COLLECTION,
|
|
82391
|
+
columns: [...NC_DETECTION_VERDICTS_COLUMNS],
|
|
82392
|
+
writeMode: "write-through",
|
|
82393
|
+
keyOf: (row) => row.trackId,
|
|
82394
|
+
toValue: (row) => ({
|
|
82395
|
+
ruleId: row.ruleId,
|
|
82396
|
+
at: row.at,
|
|
82397
|
+
row
|
|
82398
|
+
}),
|
|
82399
|
+
fromRecord: (_key, data) => {
|
|
82400
|
+
const parsed = NcDetectionVerdictRecordSchema.safeParse(data["row"]);
|
|
82401
|
+
return parsed.success ? parsed.data : null;
|
|
82402
|
+
},
|
|
82403
|
+
deviceIdOf: (row) => row.deviceId,
|
|
82404
|
+
loadLimit: 1e4
|
|
82405
|
+
};
|
|
82406
|
+
var NcDetectionVerdictLedger = class {
|
|
82407
|
+
ledger;
|
|
82408
|
+
constructor(deps) {
|
|
82409
|
+
this.ledger = new DurableLedger({
|
|
82410
|
+
spec: NC_DETECTION_VERDICTS_SPEC,
|
|
82411
|
+
kind: deps.records.kind(ncDurableRecordKinds["detection-verdicts"]),
|
|
82412
|
+
logger: deps.logger
|
|
82413
|
+
});
|
|
82414
|
+
}
|
|
82415
|
+
/** Write-through; throws on a failed write so the tap reports it. */
|
|
82416
|
+
async record(row) {
|
|
82417
|
+
await this.ledger.put(NcDetectionVerdictRecordSchema.parse(row));
|
|
82418
|
+
}
|
|
82419
|
+
};
|
|
82420
|
+
//#endregion
|
|
82265
82421
|
//#region src/notification-center/device-prune.ts
|
|
82266
82422
|
function judgeList(list, isDead) {
|
|
82267
82423
|
const dead = list.filter(isDead);
|
|
@@ -83216,6 +83372,64 @@ var NcDeviceStateCache = class {
|
|
|
83216
83372
|
}
|
|
83217
83373
|
};
|
|
83218
83374
|
//#endregion
|
|
83375
|
+
//#region src/notification-center/detection-verdict-buttons.ts
|
|
83376
|
+
var VERDICT_CORRECT_ID = "detection-verdict-correct";
|
|
83377
|
+
var VERDICT_WRONG_ID = "detection-verdict-wrong";
|
|
83378
|
+
/** `detection-verdict-wrong:<class>` — Wrong, and what it really was. */
|
|
83379
|
+
var VERDICT_WRONG_AS_PREFIX = "detection-verdict-wrong:";
|
|
83380
|
+
/** The macro classes offered as "it was really…". Short on purpose. */
|
|
83381
|
+
var VERDICT_CLASS_CHOICES = [
|
|
83382
|
+
"person",
|
|
83383
|
+
"vehicle",
|
|
83384
|
+
"animal"
|
|
83385
|
+
];
|
|
83386
|
+
/** At most this many class buttons, and only when the channel has room. */
|
|
83387
|
+
var VERDICT_MAX_CLASS_CHOICES = 2;
|
|
83388
|
+
function verdictButtonRefusal(subject) {
|
|
83389
|
+
if (!subject.askVerdict) return "rule-off";
|
|
83390
|
+
if (subject.trackId === void 0) return "no-track";
|
|
83391
|
+
if (subject.ownerUserId === void 0) return "unowned-target";
|
|
83392
|
+
return null;
|
|
83393
|
+
}
|
|
83394
|
+
/** The classes worth offering for a subject of `className`. */
|
|
83395
|
+
function verdictClassChoices(className) {
|
|
83396
|
+
return VERDICT_CLASS_CHOICES.filter((c) => c !== className).slice(0, VERDICT_MAX_CLASS_CHOICES);
|
|
83397
|
+
}
|
|
83398
|
+
/**
|
|
83399
|
+
* Which buttons a channel with `maxActions` slots gets. `maxActions` absent
|
|
83400
|
+
* (the kind catalog did not answer) is treated as the common floor of three:
|
|
83401
|
+
* the two plain choices, never the class ones. The class choices appear only
|
|
83402
|
+
* when the channel declared room for a fourth button.
|
|
83403
|
+
*/
|
|
83404
|
+
function verdictButtonSpecs(input) {
|
|
83405
|
+
const base = [{
|
|
83406
|
+
actionId: VERDICT_CORRECT_ID,
|
|
83407
|
+
verdict: "correct"
|
|
83408
|
+
}, {
|
|
83409
|
+
actionId: VERDICT_WRONG_ID,
|
|
83410
|
+
verdict: "wrong"
|
|
83411
|
+
}];
|
|
83412
|
+
if (input.maxActions === void 0 || input.maxActions <= 3) return base;
|
|
83413
|
+
const room = input.maxActions - base.length;
|
|
83414
|
+
const extra = verdictClassChoices(input.className).slice(0, room).map((suggestedClass) => ({
|
|
83415
|
+
actionId: `${VERDICT_WRONG_AS_PREFIX}${suggestedClass}`,
|
|
83416
|
+
verdict: "wrong",
|
|
83417
|
+
suggestedClass
|
|
83418
|
+
}));
|
|
83419
|
+
return [...base, ...extra];
|
|
83420
|
+
}
|
|
83421
|
+
/** Is this one of our buttons (so a link-only channel can lift it into the body)? */
|
|
83422
|
+
function isVerdictAction(action) {
|
|
83423
|
+
return action.id === VERDICT_CORRECT_ID || action.id.startsWith(VERDICT_WRONG_ID);
|
|
83424
|
+
}
|
|
83425
|
+
/**
|
|
83426
|
+
* The body lines that carry the verdict choices on a channel with no buttons.
|
|
83427
|
+
* A bare URL per line: every chat and mail client linkifies it.
|
|
83428
|
+
*/
|
|
83429
|
+
function verdictLinkLines(actions) {
|
|
83430
|
+
return actions.filter(isVerdictAction).map((a) => `${a.label}: ${a.url ?? ""}`).join("\n");
|
|
83431
|
+
}
|
|
83432
|
+
//#endregion
|
|
83219
83433
|
//#region src/notification-center/state-summary/state-model.ts
|
|
83220
83434
|
/**
|
|
83221
83435
|
* The STATE SUMMARY's vocabulary (D647): its sections, and what one reported
|
|
@@ -86924,6 +87138,8 @@ var NcDispatcher = class {
|
|
|
86924
87138
|
targetId: target.id,
|
|
86925
87139
|
...target.ownerUserId !== void 0 ? { ownerUserId: target.ownerUserId } : {},
|
|
86926
87140
|
...entry.payload.subject.className !== void 0 ? { className: entry.payload.subject.className } : {},
|
|
87141
|
+
...entry.payload.subject.trackId !== void 0 ? { trackId: entry.payload.subject.trackId } : {},
|
|
87142
|
+
...target.maxActions !== void 0 ? { maxActions: target.maxActions } : {},
|
|
86927
87143
|
...entry.payload.subject.systemEvent !== void 0 ? { systemEvent: entry.payload.subject.systemEvent } : {}
|
|
86928
87144
|
});
|
|
86929
87145
|
} catch (err) {
|
|
@@ -87019,9 +87235,13 @@ var NcDispatcher = class {
|
|
|
87019
87235
|
...summarySectionOf(systemEvent.data)
|
|
87020
87236
|
} } : {}
|
|
87021
87237
|
}) : null;
|
|
87022
|
-
const
|
|
87238
|
+
const minted = await this.mintActions(entry, target);
|
|
87239
|
+
const linkOnly = target.maxActions === 0;
|
|
87240
|
+
const verdictLinks = linkOnly ? verdictLinkLines(minted) : "";
|
|
87241
|
+
const actions = linkOnly ? minted.filter((a) => !isVerdictAction(a)) : minted;
|
|
87242
|
+
const withLinks = (text) => verdictLinks === "" ? text : `${text}\n\n${verdictLinks}`;
|
|
87023
87243
|
const notification = {
|
|
87024
|
-
body,
|
|
87244
|
+
body: withLinks(body),
|
|
87025
87245
|
title,
|
|
87026
87246
|
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
87027
87247
|
...params,
|
|
@@ -87043,7 +87263,7 @@ var NcDispatcher = class {
|
|
|
87043
87263
|
grouped: resolved.grouped,
|
|
87044
87264
|
withAiText: (aiText) => ({
|
|
87045
87265
|
...notification,
|
|
87046
|
-
body: renderBody(aiText)
|
|
87266
|
+
body: withLinks(renderBody(aiText))
|
|
87047
87267
|
})
|
|
87048
87268
|
};
|
|
87049
87269
|
}
|
|
@@ -99656,6 +99876,55 @@ async function mintButtons(host, input) {
|
|
|
99656
99876
|
const announcement = stored === null && input.ruleId === NC_ALARM_ANNOUNCE_RULE_ID;
|
|
99657
99877
|
if (stored === null && !announcement) return [];
|
|
99658
99878
|
const drafts = [];
|
|
99879
|
+
const verdictRefusal = verdictButtonRefusal({
|
|
99880
|
+
askVerdict: stored?.askVerdict === true,
|
|
99881
|
+
...input.trackId !== void 0 ? { trackId: input.trackId } : {},
|
|
99882
|
+
...input.ownerUserId !== void 0 ? { ownerUserId: input.ownerUserId } : {}
|
|
99883
|
+
});
|
|
99884
|
+
if (stored?.askVerdict === true && verdictRefusal !== null) host.logger.info("detection notification carries no verdict buttons", {
|
|
99885
|
+
tags: { deviceId: input.deviceId },
|
|
99886
|
+
meta: {
|
|
99887
|
+
ruleId: input.ruleId,
|
|
99888
|
+
reason: verdictRefusal,
|
|
99889
|
+
targetId: input.targetId
|
|
99890
|
+
}
|
|
99891
|
+
});
|
|
99892
|
+
const verdictTrackId = input.trackId;
|
|
99893
|
+
const verdictOwner = input.ownerUserId;
|
|
99894
|
+
const verdictButtons = verdictRefusal !== null || verdictTrackId === void 0 || verdictOwner === void 0 ? [] : verdictButtonSpecs({
|
|
99895
|
+
className: input.className,
|
|
99896
|
+
maxActions: input.maxActions
|
|
99897
|
+
}).map((spec) => {
|
|
99898
|
+
const id = randomUUID();
|
|
99899
|
+
drafts.push({
|
|
99900
|
+
id,
|
|
99901
|
+
ttlMs: actionTtlMs({ kind: "detection-verdict" }),
|
|
99902
|
+
grant: (expiresAt) => ({
|
|
99903
|
+
kind: "detection-verdict",
|
|
99904
|
+
id,
|
|
99905
|
+
verdict: spec.verdict,
|
|
99906
|
+
trackId: verdictTrackId,
|
|
99907
|
+
ruleId: input.ruleId,
|
|
99908
|
+
ruleName: input.ruleName,
|
|
99909
|
+
userId: verdictOwner,
|
|
99910
|
+
...spec.suggestedClass !== void 0 ? { suggestedClass: spec.suggestedClass } : {},
|
|
99911
|
+
...input.maxActions === 0 ? { viaLink: true } : {},
|
|
99912
|
+
deviceId: input.deviceId,
|
|
99913
|
+
actionId: spec.actionId,
|
|
99914
|
+
targetId: input.targetId,
|
|
99915
|
+
expiresAt
|
|
99916
|
+
})
|
|
99917
|
+
});
|
|
99918
|
+
return {
|
|
99919
|
+
id: spec.actionId,
|
|
99920
|
+
label: spec.suggestedClass === void 0 ? host.texts.text({ key: spec.verdict === "correct" ? "button.verdict.correct" : "button.verdict.wrong" }) : host.texts.text({
|
|
99921
|
+
key: "button.verdict.wrongAs",
|
|
99922
|
+
vars: { class: spec.suggestedClass }
|
|
99923
|
+
}),
|
|
99924
|
+
url: id,
|
|
99925
|
+
mode: "background"
|
|
99926
|
+
};
|
|
99927
|
+
});
|
|
99659
99928
|
const sequenceButtons = host.actionRunner === null || stored === null ? [] : buildActionButtons({
|
|
99660
99929
|
actions: stored.actions,
|
|
99661
99930
|
knownSequenceIds: host.sequences.knownIds(),
|
|
@@ -99868,6 +100137,7 @@ async function mintButtons(host, input) {
|
|
|
99868
100137
|
}
|
|
99869
100138
|
});
|
|
99870
100139
|
const out = [
|
|
100140
|
+
...verdictButtons,
|
|
99871
100141
|
...sequenceButtons,
|
|
99872
100142
|
...snoozeButtons,
|
|
99873
100143
|
...alarmButtons,
|
|
@@ -100091,8 +100361,53 @@ async function runGrantedAction(host, grant) {
|
|
|
100091
100361
|
case "sequence": return host.runGrantedSequence(grant);
|
|
100092
100362
|
case "addon-update": return host.applyGrantedAddonUpdate(grant);
|
|
100093
100363
|
case "server-update": return host.applyGrantedServerUpdate(grant);
|
|
100364
|
+
case "detection-verdict": return host.applyGrantedDetectionVerdict(grant);
|
|
100094
100365
|
}
|
|
100095
100366
|
}
|
|
100367
|
+
/** What a person is told when the retrain lifecycle refuses the mark. */
|
|
100368
|
+
var VERDICT_REFUSAL_COPY = {
|
|
100369
|
+
trained: "This track was already used for training — nothing was changed",
|
|
100370
|
+
budget: "This camera already holds as many tracks for retraining as it may — nothing was changed",
|
|
100371
|
+
"unknown-track": "This track is no longer available — nothing was changed"
|
|
100372
|
+
};
|
|
100373
|
+
/**
|
|
100374
|
+
* Mark the track one notification reported for retraining, and record who was
|
|
100375
|
+
* asked and when.
|
|
100376
|
+
*
|
|
100377
|
+
* Order matters. The MARK goes first and is the authority: a refusal ("already
|
|
100378
|
+
* trained", "budget full") means the operator's answer changed nothing, so no
|
|
100379
|
+
* ledger row is written for an answer that was not applied. The token is
|
|
100380
|
+
* already spent (D47) — the refusal is shown as the reason, not a retry.
|
|
100381
|
+
*/
|
|
100382
|
+
async function applyGrantedDetectionVerdict(host, grant) {
|
|
100383
|
+
const mark = host.deps.markTrackForRetrain;
|
|
100384
|
+
if (mark === void 0) throw new Error("no retrain mark is wired on this node");
|
|
100385
|
+
const outcome = await mark({
|
|
100386
|
+
deviceId: grant.deviceId,
|
|
100387
|
+
trackId: grant.trackId
|
|
100388
|
+
});
|
|
100389
|
+
if (!outcome.ok) {
|
|
100390
|
+
host.logger.info("detection verdict changed nothing — the retrain flow refused the mark", {
|
|
100391
|
+
tags: { deviceId: grant.deviceId },
|
|
100392
|
+
meta: {
|
|
100393
|
+
trackId: grant.trackId,
|
|
100394
|
+
reason: outcome.reason,
|
|
100395
|
+
verdict: grant.verdict
|
|
100396
|
+
}
|
|
100397
|
+
});
|
|
100398
|
+
throw new NcActionRefusedError(VERDICT_REFUSAL_COPY[outcome.reason]);
|
|
100399
|
+
}
|
|
100400
|
+
await host.verdicts.record({
|
|
100401
|
+
trackId: grant.trackId,
|
|
100402
|
+
deviceId: grant.deviceId,
|
|
100403
|
+
verdict: grant.verdict,
|
|
100404
|
+
...grant.suggestedClass !== void 0 ? { suggestedClass: grant.suggestedClass } : {},
|
|
100405
|
+
userId: grant.userId,
|
|
100406
|
+
ruleId: grant.ruleId,
|
|
100407
|
+
ruleName: grant.ruleName,
|
|
100408
|
+
at: host.now()
|
|
100409
|
+
});
|
|
100410
|
+
}
|
|
100096
100411
|
/**
|
|
100097
100412
|
* Apply the addon updates one notification was about.
|
|
100098
100413
|
*
|
|
@@ -105269,6 +105584,7 @@ var NotificationCenter = class {
|
|
|
105269
105584
|
* evaluating one.
|
|
105270
105585
|
*/
|
|
105271
105586
|
pruneLedger;
|
|
105587
|
+
verdicts;
|
|
105272
105588
|
/** D905: which rules cannot work, announced once per distinct reason. */
|
|
105273
105589
|
ruleHealth = new NcRuleHealthMonitor();
|
|
105274
105590
|
/** The prune itself — see `device-prune-runner.ts`. */
|
|
@@ -105497,6 +105813,11 @@ var NotificationCenter = class {
|
|
|
105497
105813
|
applyGrantedArmAnyway: (panel, grant) => self.applyGrantedArmAnyway(panel, grant),
|
|
105498
105814
|
applyGrantedBypassInput: (panel, grant) => self.applyGrantedBypassInput(panel, grant),
|
|
105499
105815
|
applyGrantedServerUpdate: (grant) => self.applyGrantedServerUpdate(grant),
|
|
105816
|
+
applyGrantedDetectionVerdict: (grant) => self.applyGrantedDetectionVerdict(grant),
|
|
105817
|
+
now: self.now,
|
|
105818
|
+
get verdicts() {
|
|
105819
|
+
return self.verdicts;
|
|
105820
|
+
},
|
|
105500
105821
|
applyGrantedSnooze: (grant) => self.applyGrantedSnooze(grant),
|
|
105501
105822
|
get deps() {
|
|
105502
105823
|
return self.deps;
|
|
@@ -105547,6 +105868,9 @@ var NotificationCenter = class {
|
|
|
105547
105868
|
async applyGrantedAddonUpdate(grant) {
|
|
105548
105869
|
return applyGrantedAddonUpdate(this.grantedActionsHost(), grant);
|
|
105549
105870
|
}
|
|
105871
|
+
async applyGrantedDetectionVerdict(grant) {
|
|
105872
|
+
return applyGrantedDetectionVerdict(this.grantedActionsHost(), grant);
|
|
105873
|
+
}
|
|
105550
105874
|
async applyGrantedServerUpdate(grant) {
|
|
105551
105875
|
return applyGrantedServerUpdate(this.grantedActionsHost(), grant);
|
|
105552
105876
|
}
|
|
@@ -105786,6 +106110,10 @@ var NotificationCenter = class {
|
|
|
105786
106110
|
}
|
|
105787
106111
|
this.summaryRules = NcSummaryStore.of(deps.records, this.logger.child("summary"), deps.now);
|
|
105788
106112
|
this.calendarEventsRules = NcCalendarEventsStore.of(deps.records, this.logger.child("calendar-events"), deps.now);
|
|
106113
|
+
this.verdicts = new NcDetectionVerdictLedger({
|
|
106114
|
+
...durableStore,
|
|
106115
|
+
logger: this.logger.child("detection-verdicts")
|
|
106116
|
+
});
|
|
105789
106117
|
this.pruneLedger = new NcPruneLedger({
|
|
105790
106118
|
...durableStore,
|
|
105791
106119
|
logger: this.logger.child("device-prune")
|
|
@@ -108393,6 +108721,23 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108393
108721
|
};
|
|
108394
108722
|
}
|
|
108395
108723
|
},
|
|
108724
|
+
markTrackForRetrain: async ({ deviceId, trackId }) => {
|
|
108725
|
+
try {
|
|
108726
|
+
await this.ctx.api.pipelineAnalytics.setTrackFlags.mutate({
|
|
108727
|
+
deviceId,
|
|
108728
|
+
trackId,
|
|
108729
|
+
flags: { markForTrain: true }
|
|
108730
|
+
});
|
|
108731
|
+
return { ok: true };
|
|
108732
|
+
} catch (err) {
|
|
108733
|
+
const reason = retrainRefusalReason(err);
|
|
108734
|
+
if (reason === null) throw err;
|
|
108735
|
+
return {
|
|
108736
|
+
ok: false,
|
|
108737
|
+
reason
|
|
108738
|
+
};
|
|
108739
|
+
}
|
|
108740
|
+
},
|
|
108396
108741
|
readDeviceStates: async (ids) => {
|
|
108397
108742
|
const out = /* @__PURE__ */ new Map();
|
|
108398
108743
|
for (const id of ids) try {
|
|
@@ -108464,9 +108809,11 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108464
108809
|
const targets = await api.notificationOutput.listTargets.query({});
|
|
108465
108810
|
const kinds = await api.notificationOutput.listTargetKinds.query({}).catch(() => []);
|
|
108466
108811
|
const bodyMaxLenByKind = new Map(kinds.map((k) => [k.kind, k.caps.bodyMaxLen]));
|
|
108812
|
+
const maxActionsByKind = new Map(kinds.map((k) => [k.kind, k.caps.actions]));
|
|
108467
108813
|
return targets.map((t) => {
|
|
108468
108814
|
const owner = t.config["ownerUserId"];
|
|
108469
108815
|
const bodyMaxLen = bodyMaxLenByKind.get(t.kind);
|
|
108816
|
+
const maxActions = maxActionsByKind.get(t.kind);
|
|
108470
108817
|
return {
|
|
108471
108818
|
id: t.id,
|
|
108472
108819
|
addonId: t.addonId,
|
|
@@ -108474,7 +108821,8 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
108474
108821
|
kind: t.kind,
|
|
108475
108822
|
enabled: t.enabled,
|
|
108476
108823
|
...typeof owner === "string" && owner.length > 0 ? { ownerUserId: owner } : {},
|
|
108477
|
-
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {}
|
|
108824
|
+
...typeof bodyMaxLen === "number" ? { bodyMaxLen } : {},
|
|
108825
|
+
...typeof maxActions === "number" ? { maxActions } : {}
|
|
108478
108826
|
};
|
|
108479
108827
|
});
|
|
108480
108828
|
},
|
|
@@ -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_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Dhan9FH4.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-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.642",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|