@trillboards/edge-sdk 0.2.39 → 0.2.41
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/README.md +1 -1
- package/deploy/docker/{Dockerfile.cloud-sense → Dockerfile.connector} +52 -39
- package/deploy/docker/README.md +73 -1
- package/deploy/docker/connector-entrypoint.sh +46 -80
- package/deploy/macos/README.md +1 -1
- package/deploy/macos/test-install-pair.sh +9 -9
- package/deploy/systemd/install.sh +16 -1
- package/deploy/systemd/setup-litertlm.sh +201 -0
- package/deploy/systemd/trillboards-edge.service +8 -4
- package/deploy/systemd/trillboards-litertlm.service +46 -0
- package/deploy/windows/register-task.ps1 +6 -3
- package/deploy/windows/run-connector.ps1 +1 -1
- package/deploy/windows/trillboards-edge-task.vbs +5 -1
- package/dist/CloudSensingManager.d.ts +45 -19
- package/dist/CloudSensingManager.d.ts.map +1 -1
- package/dist/CloudSensingManager.js +89 -46
- package/dist/CloudSensingManager.js.map +1 -1
- package/dist/EdgeAgent.js +1 -1
- package/dist/SensingProfileClient.d.ts +9 -0
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js +4 -0
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/SetupSessionClient.d.ts +0 -1
- package/dist/SetupSessionClient.d.ts.map +1 -1
- package/dist/autoSense.d.ts +42 -31
- package/dist/autoSense.d.ts.map +1 -1
- package/dist/autoSense.js +127 -99
- package/dist/autoSense.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +25 -84
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +1 -3
- package/dist/combinedSense.d.ts.map +1 -1
- package/dist/combinedSense.js +0 -1
- package/dist/combinedSense.js.map +1 -1
- package/dist/config.d.ts +17 -64
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -10
- package/dist/config.js.map +1 -1
- package/dist/deployedCadence.d.ts +37 -75
- package/dist/deployedCadence.d.ts.map +1 -1
- package/dist/deployedCadence.js +45 -105
- package/dist/deployedCadence.js.map +1 -1
- package/dist/deployedCadenceSource.d.ts +0 -2
- package/dist/deployedCadenceSource.d.ts.map +1 -1
- package/dist/deployedCadenceSource.js +0 -1
- package/dist/deployedCadenceSource.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -8
- package/dist/index.js.map +1 -1
- package/dist/localInferenceSchema.d.ts +19 -0
- package/dist/localInferenceSchema.d.ts.map +1 -1
- package/dist/localInferenceSchema.js +23 -0
- package/dist/localInferenceSchema.js.map +1 -1
- package/dist/localSensing.d.ts +48 -107
- package/dist/localSensing.d.ts.map +1 -1
- package/dist/localSensing.js +80 -171
- package/dist/localSensing.js.map +1 -1
- package/dist/sensingCatchRate.d.ts +246 -0
- package/dist/sensingCatchRate.d.ts.map +1 -0
- package/dist/sensingCatchRate.js +514 -0
- package/dist/sensingCatchRate.js.map +1 -0
- package/dist/sensingRunMode.d.ts +14 -11
- package/dist/sensingRunMode.d.ts.map +1 -1
- package/dist/sensingRunMode.js +9 -92
- package/dist/sensingRunMode.js.map +1 -1
- package/dist/setupMode.d.ts +0 -1
- package/dist/setupMode.d.ts.map +1 -1
- package/dist/setupMode.js +5 -3
- package/dist/setupMode.js.map +1 -1
- package/dist/startupDiscovery.d.ts +43 -0
- package/dist/startupDiscovery.d.ts.map +1 -0
- package/dist/startupDiscovery.js +72 -0
- package/dist/startupDiscovery.js.map +1 -0
- package/package.json +1 -1
- package/dist/CloudEscalationPolicy.d.ts +0 -143
- package/dist/CloudEscalationPolicy.d.ts.map +0 -1
- package/dist/CloudEscalationPolicy.js +0 -137
- package/dist/CloudEscalationPolicy.js.map +0 -1
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CloudEscalationPolicy — WHEN a locally-inferred candidate is worth one cloud
|
|
3
|
-
* VLM call, and when it is not.
|
|
4
|
-
*
|
|
5
|
-
* ── WHY THIS EXISTS ─────────────────────────────────────────────────────────
|
|
6
|
-
* The connector's on-device Gemma is an excellent PRESENCE detector and an
|
|
7
|
-
* unreliable JUDGE. Measured through the shipped code path against `litert-lm
|
|
8
|
-
* serve` on real 640x480 q70 frames (2026-08-12, M4 Max, GPU delegate): the
|
|
9
|
-
* empty-scene verdict was right every single run, while the operator's SOP
|
|
10
|
-
* booleans on a 5-person kitchen frame flipped between runs at temperature 0 —
|
|
11
|
-
* and it reported `confidence` 0.95 on the runs it got WRONG. So the local model
|
|
12
|
-
* is trustworthy for "is anything happening here?" and not for "did the thing
|
|
13
|
-
* the operator cares about actually happen?".
|
|
14
|
-
*
|
|
15
|
-
* Escalation makes the second question the CLOUD's: local Gemma stays the
|
|
16
|
-
* always-on gate, and the one frame it flags is re-analyzed by the cloud VLM
|
|
17
|
-
* before an alert can fire.
|
|
18
|
-
*
|
|
19
|
-
* ── WHY A POLICY OBJECT AND NOT AN `if` ─────────────────────────────────────
|
|
20
|
-
* A cloud call costs money (~$0.002) and, more importantly, sends a frame off
|
|
21
|
-
* the customer's premises. Both have to be BOUNDED by construction, not by good
|
|
22
|
-
* intentions, because the pathological case is real: a camera pointed at a busy
|
|
23
|
-
* pavement produces a candidate on EVERY frame forever. Three independent
|
|
24
|
-
* bounds, cheapest first:
|
|
25
|
-
*
|
|
26
|
-
* 1. NOVELTY (`reconfirmAfterMs`) — a scene whose alert-bearing verdict has
|
|
27
|
-
* not CHANGED is not re-asked. This is what makes a permanently-occupied
|
|
28
|
-
* camera cost one confirmation per reconfirm window instead of one per
|
|
29
|
-
* frame; it is the structural bound, not a rate limit.
|
|
30
|
-
* 2. PACING (`minIntervalMs`) — a floor between two escalations on one feed,
|
|
31
|
-
* so a rapidly-changing scene cannot spend the hour's budget in a minute.
|
|
32
|
-
* 3. BUDGET (`maxPerHour`) — a hard sliding-window ceiling per feed. Whatever
|
|
33
|
-
* the scene does, this is the most a camera can ever cost.
|
|
34
|
-
*
|
|
35
|
-
* The caller supplies the SCENE facts (`hasSignal`, `signature`); this class
|
|
36
|
-
* owns only rate + novelty. That split is deliberate: `localSensing.isAlertWorthy`
|
|
37
|
-
* stays the ONE authority on "does this frame carry any signal", so the empty-
|
|
38
|
-
* scene privacy gate can never drift apart from the escalation gate.
|
|
39
|
-
*
|
|
40
|
-
* Mirrors `VlmAdmissionGate`'s idiom: `tryEscalate()` COMMITS when it returns
|
|
41
|
-
* `escalate: true`, so a decision cannot be counted twice or forgotten.
|
|
42
|
-
*/
|
|
43
|
-
/** Why an escalation was declined (or granted). Surfaced in stats + logs. */
|
|
44
|
-
export type EscalationReason =
|
|
45
|
-
/** Granted — this frame goes to the cloud VLM for adjudication. */
|
|
46
|
-
'candidate'
|
|
47
|
-
/** Feature off (the default). */
|
|
48
|
-
| 'disabled'
|
|
49
|
-
/** The frame carries no signal at all — an empty scene never escalates. */
|
|
50
|
-
| 'no_signal'
|
|
51
|
-
/** Same alert-bearing verdict as the last escalation, inside the reconfirm window. */
|
|
52
|
-
| 'unchanged'
|
|
53
|
-
/** Too soon after the previous escalation on this feed. */
|
|
54
|
-
| 'min_interval'
|
|
55
|
-
/** This feed has spent its hourly budget. */
|
|
56
|
-
| 'hourly_budget';
|
|
57
|
-
export interface EscalationVerdict {
|
|
58
|
-
escalate: boolean;
|
|
59
|
-
reason: EscalationReason;
|
|
60
|
-
}
|
|
61
|
-
/** The scene facts the caller has already computed for this frame. */
|
|
62
|
-
export interface EscalationInput {
|
|
63
|
-
/**
|
|
64
|
-
* Does this frame carry ANY meaningful signal? Supplied by
|
|
65
|
-
* `localSensing.isAlertWorthy` — the same gate that decides whether an
|
|
66
|
-
* evidence JPEG may be attached, so escalation can never send a frame the
|
|
67
|
-
* privacy gate would have withheld.
|
|
68
|
-
*/
|
|
69
|
-
hasSignal: boolean;
|
|
70
|
-
/**
|
|
71
|
-
* Stable fingerprint of the ALERT-BEARING fields of this result (see
|
|
72
|
-
* `localSensing.alertSignature`). Equal signatures mean "the operator-visible
|
|
73
|
-
* state did not change", which is what the novelty bound keys on.
|
|
74
|
-
*/
|
|
75
|
-
signature: string;
|
|
76
|
-
}
|
|
77
|
-
export interface CloudEscalationPolicyOptions {
|
|
78
|
-
/**
|
|
79
|
-
* Master switch. Default FALSE: sending a frame off-premises is an explicit
|
|
80
|
-
* operator decision, never a silent default — the same standing rule that
|
|
81
|
-
* makes `localSensing.allowCloudFallback` opt-in.
|
|
82
|
-
*/
|
|
83
|
-
enabled?: boolean;
|
|
84
|
-
/** Floor (ms) between two escalations on this feed. Default 60000. */
|
|
85
|
-
minIntervalMs?: number;
|
|
86
|
-
/** Hard ceiling on escalations per rolling hour for this feed. Default 12. */
|
|
87
|
-
maxPerHour?: number;
|
|
88
|
-
/**
|
|
89
|
-
* How long an UNCHANGED verdict stays confirmed before the cloud is asked
|
|
90
|
-
* again (ms). Default 900000 (15 min). 0 re-confirms every candidate frame
|
|
91
|
-
* (bounded only by pacing + budget); a very large value confirms a stable
|
|
92
|
-
* scene exactly once.
|
|
93
|
-
*/
|
|
94
|
-
reconfirmAfterMs?: number;
|
|
95
|
-
/** Clock injection for tests. Default `Date.now`. */
|
|
96
|
-
now?: () => number;
|
|
97
|
-
}
|
|
98
|
-
export interface CloudEscalationStats {
|
|
99
|
-
enabled: boolean;
|
|
100
|
-
/** Escalations granted (a cloud call was made). */
|
|
101
|
-
escalated: number;
|
|
102
|
-
/** Candidate frames declined, by reason. */
|
|
103
|
-
skippedUnchanged: number;
|
|
104
|
-
skippedMinInterval: number;
|
|
105
|
-
skippedBudget: number;
|
|
106
|
-
/** Frames that were never candidates (no signal). Cheap, and the common case. */
|
|
107
|
-
skippedNoSignal: number;
|
|
108
|
-
/** Escalations still available in the current rolling hour. */
|
|
109
|
-
remainingThisHour: number;
|
|
110
|
-
lastEscalatedAtMs: number | null;
|
|
111
|
-
}
|
|
112
|
-
export declare class CloudEscalationPolicy {
|
|
113
|
-
private readonly enabled;
|
|
114
|
-
private readonly minIntervalMs;
|
|
115
|
-
private readonly maxPerHour;
|
|
116
|
-
private readonly reconfirmAfterMs;
|
|
117
|
-
private readonly now;
|
|
118
|
-
/** Escalation timestamps inside the rolling hour (never longer than maxPerHour). */
|
|
119
|
-
private readonly window;
|
|
120
|
-
private lastEscalatedAtMs;
|
|
121
|
-
private lastSignature;
|
|
122
|
-
private escalated;
|
|
123
|
-
private skippedUnchanged;
|
|
124
|
-
private skippedMinInterval;
|
|
125
|
-
private skippedBudget;
|
|
126
|
-
private skippedNoSignal;
|
|
127
|
-
constructor(options?: CloudEscalationPolicyOptions);
|
|
128
|
-
isEnabled(): boolean;
|
|
129
|
-
/**
|
|
130
|
-
* Decide — and COMMIT — whether this frame goes to the cloud.
|
|
131
|
-
*
|
|
132
|
-
* Returning `{ escalate: true }` records the escalation against the pacing
|
|
133
|
-
* floor, the hourly budget and the novelty memory, so a caller cannot ask
|
|
134
|
-
* twice for one frame. A cloud call that then FAILS is deliberately NOT
|
|
135
|
-
* refunded: a broken cloud path must decay to the local verdict at the
|
|
136
|
-
* budgeted rate rather than retrying every frame.
|
|
137
|
-
*/
|
|
138
|
-
tryEscalate(input: EscalationInput, nowMs?: number): EscalationVerdict;
|
|
139
|
-
getStats(nowMs?: number): CloudEscalationStats;
|
|
140
|
-
/** Drop escalation timestamps older than the rolling hour. */
|
|
141
|
-
private pruneWindow;
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=CloudEscalationPolicy.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CloudEscalationPolicy.d.ts","sourceRoot":"","sources":["../src/CloudEscalationPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,gBAAgB;AAC1B,mEAAmE;AACjE,WAAW;AACb,iCAAiC;GAC/B,UAAU;AACZ,2EAA2E;GACzE,WAAW;AACb,sFAAsF;GACpF,WAAW;AACb,2DAA2D;GACzD,cAAc;AAChB,6CAA6C;GAC3C,eAAe,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAOD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,aAAa,CAAuB;IAE5C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,eAAe,CAAK;gBAEhB,OAAO,GAAE,4BAAiC;IAWtD,SAAS,IAAI,OAAO;IAIpB;;;;;;;;OAQG;IACH,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,iBAAiB;IA2CtE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,oBAAoB;IAe9C,8DAA8D;IAC9D,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* CloudEscalationPolicy — WHEN a locally-inferred candidate is worth one cloud
|
|
4
|
-
* VLM call, and when it is not.
|
|
5
|
-
*
|
|
6
|
-
* ── WHY THIS EXISTS ─────────────────────────────────────────────────────────
|
|
7
|
-
* The connector's on-device Gemma is an excellent PRESENCE detector and an
|
|
8
|
-
* unreliable JUDGE. Measured through the shipped code path against `litert-lm
|
|
9
|
-
* serve` on real 640x480 q70 frames (2026-08-12, M4 Max, GPU delegate): the
|
|
10
|
-
* empty-scene verdict was right every single run, while the operator's SOP
|
|
11
|
-
* booleans on a 5-person kitchen frame flipped between runs at temperature 0 —
|
|
12
|
-
* and it reported `confidence` 0.95 on the runs it got WRONG. So the local model
|
|
13
|
-
* is trustworthy for "is anything happening here?" and not for "did the thing
|
|
14
|
-
* the operator cares about actually happen?".
|
|
15
|
-
*
|
|
16
|
-
* Escalation makes the second question the CLOUD's: local Gemma stays the
|
|
17
|
-
* always-on gate, and the one frame it flags is re-analyzed by the cloud VLM
|
|
18
|
-
* before an alert can fire.
|
|
19
|
-
*
|
|
20
|
-
* ── WHY A POLICY OBJECT AND NOT AN `if` ─────────────────────────────────────
|
|
21
|
-
* A cloud call costs money (~$0.002) and, more importantly, sends a frame off
|
|
22
|
-
* the customer's premises. Both have to be BOUNDED by construction, not by good
|
|
23
|
-
* intentions, because the pathological case is real: a camera pointed at a busy
|
|
24
|
-
* pavement produces a candidate on EVERY frame forever. Three independent
|
|
25
|
-
* bounds, cheapest first:
|
|
26
|
-
*
|
|
27
|
-
* 1. NOVELTY (`reconfirmAfterMs`) — a scene whose alert-bearing verdict has
|
|
28
|
-
* not CHANGED is not re-asked. This is what makes a permanently-occupied
|
|
29
|
-
* camera cost one confirmation per reconfirm window instead of one per
|
|
30
|
-
* frame; it is the structural bound, not a rate limit.
|
|
31
|
-
* 2. PACING (`minIntervalMs`) — a floor between two escalations on one feed,
|
|
32
|
-
* so a rapidly-changing scene cannot spend the hour's budget in a minute.
|
|
33
|
-
* 3. BUDGET (`maxPerHour`) — a hard sliding-window ceiling per feed. Whatever
|
|
34
|
-
* the scene does, this is the most a camera can ever cost.
|
|
35
|
-
*
|
|
36
|
-
* The caller supplies the SCENE facts (`hasSignal`, `signature`); this class
|
|
37
|
-
* owns only rate + novelty. That split is deliberate: `localSensing.isAlertWorthy`
|
|
38
|
-
* stays the ONE authority on "does this frame carry any signal", so the empty-
|
|
39
|
-
* scene privacy gate can never drift apart from the escalation gate.
|
|
40
|
-
*
|
|
41
|
-
* Mirrors `VlmAdmissionGate`'s idiom: `tryEscalate()` COMMITS when it returns
|
|
42
|
-
* `escalate: true`, so a decision cannot be counted twice or forgotten.
|
|
43
|
-
*/
|
|
44
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.CloudEscalationPolicy = void 0;
|
|
46
|
-
const DEFAULT_MIN_INTERVAL_MS = 60000;
|
|
47
|
-
const DEFAULT_MAX_PER_HOUR = 12;
|
|
48
|
-
const DEFAULT_RECONFIRM_AFTER_MS = 15 * 60000;
|
|
49
|
-
const HOUR_MS = 3600000;
|
|
50
|
-
class CloudEscalationPolicy {
|
|
51
|
-
constructor(options = {}) {
|
|
52
|
-
/** Escalation timestamps inside the rolling hour (never longer than maxPerHour). */
|
|
53
|
-
this.window = [];
|
|
54
|
-
this.lastEscalatedAtMs = null;
|
|
55
|
-
this.lastSignature = null;
|
|
56
|
-
this.escalated = 0;
|
|
57
|
-
this.skippedUnchanged = 0;
|
|
58
|
-
this.skippedMinInterval = 0;
|
|
59
|
-
this.skippedBudget = 0;
|
|
60
|
-
this.skippedNoSignal = 0;
|
|
61
|
-
this.enabled = options.enabled === true;
|
|
62
|
-
this.minIntervalMs = Math.max(0, Math.trunc(options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS));
|
|
63
|
-
this.maxPerHour = Math.max(0, Math.trunc(options.maxPerHour ?? DEFAULT_MAX_PER_HOUR));
|
|
64
|
-
this.reconfirmAfterMs = Math.max(0, Math.trunc(options.reconfirmAfterMs ?? DEFAULT_RECONFIRM_AFTER_MS));
|
|
65
|
-
this.now = options.now ?? Date.now;
|
|
66
|
-
}
|
|
67
|
-
isEnabled() {
|
|
68
|
-
return this.enabled;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Decide — and COMMIT — whether this frame goes to the cloud.
|
|
72
|
-
*
|
|
73
|
-
* Returning `{ escalate: true }` records the escalation against the pacing
|
|
74
|
-
* floor, the hourly budget and the novelty memory, so a caller cannot ask
|
|
75
|
-
* twice for one frame. A cloud call that then FAILS is deliberately NOT
|
|
76
|
-
* refunded: a broken cloud path must decay to the local verdict at the
|
|
77
|
-
* budgeted rate rather than retrying every frame.
|
|
78
|
-
*/
|
|
79
|
-
tryEscalate(input, nowMs) {
|
|
80
|
-
if (!this.enabled)
|
|
81
|
-
return { escalate: false, reason: 'disabled' };
|
|
82
|
-
if (!input.hasSignal) {
|
|
83
|
-
this.skippedNoSignal++;
|
|
84
|
-
return { escalate: false, reason: 'no_signal' };
|
|
85
|
-
}
|
|
86
|
-
const now = nowMs ?? this.now();
|
|
87
|
-
this.pruneWindow(now);
|
|
88
|
-
// 1. NOVELTY — an unchanged verdict inside the reconfirm window is already
|
|
89
|
-
// adjudicated; asking again buys nothing and costs a frame leaving the
|
|
90
|
-
// building. This is the bound that makes a permanently-busy camera cheap.
|
|
91
|
-
if (this.lastSignature !== null &&
|
|
92
|
-
this.lastSignature === input.signature &&
|
|
93
|
-
this.lastEscalatedAtMs !== null &&
|
|
94
|
-
now - this.lastEscalatedAtMs < this.reconfirmAfterMs) {
|
|
95
|
-
this.skippedUnchanged++;
|
|
96
|
-
return { escalate: false, reason: 'unchanged' };
|
|
97
|
-
}
|
|
98
|
-
// 2. PACING — a scene that changes every frame still cannot outrun this.
|
|
99
|
-
if (this.lastEscalatedAtMs !== null && now - this.lastEscalatedAtMs < this.minIntervalMs) {
|
|
100
|
-
this.skippedMinInterval++;
|
|
101
|
-
return { escalate: false, reason: 'min_interval' };
|
|
102
|
-
}
|
|
103
|
-
// 3. BUDGET — the hard ceiling. `maxPerHour: 0` disables escalation
|
|
104
|
-
// entirely, which is a legitimate (belt-and-braces) operator setting.
|
|
105
|
-
if (this.window.length >= this.maxPerHour) {
|
|
106
|
-
this.skippedBudget++;
|
|
107
|
-
return { escalate: false, reason: 'hourly_budget' };
|
|
108
|
-
}
|
|
109
|
-
this.window.push(now);
|
|
110
|
-
this.lastEscalatedAtMs = now;
|
|
111
|
-
this.lastSignature = input.signature;
|
|
112
|
-
this.escalated++;
|
|
113
|
-
return { escalate: true, reason: 'candidate' };
|
|
114
|
-
}
|
|
115
|
-
getStats(nowMs) {
|
|
116
|
-
const now = nowMs ?? this.now();
|
|
117
|
-
this.pruneWindow(now);
|
|
118
|
-
return {
|
|
119
|
-
enabled: this.enabled,
|
|
120
|
-
escalated: this.escalated,
|
|
121
|
-
skippedUnchanged: this.skippedUnchanged,
|
|
122
|
-
skippedMinInterval: this.skippedMinInterval,
|
|
123
|
-
skippedBudget: this.skippedBudget,
|
|
124
|
-
skippedNoSignal: this.skippedNoSignal,
|
|
125
|
-
remainingThisHour: Math.max(0, this.maxPerHour - this.window.length),
|
|
126
|
-
lastEscalatedAtMs: this.lastEscalatedAtMs,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
/** Drop escalation timestamps older than the rolling hour. */
|
|
130
|
-
pruneWindow(nowMs) {
|
|
131
|
-
while (this.window.length > 0 && nowMs - this.window[0] >= HOUR_MS) {
|
|
132
|
-
this.window.shift();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
exports.CloudEscalationPolicy = CloudEscalationPolicy;
|
|
137
|
-
//# sourceMappingURL=CloudEscalationPolicy.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CloudEscalationPolicy.js","sourceRoot":"","sources":["../src/CloudEscalationPolicy.ts"],"names":[],"mappings":"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"}
|