@camstack/types 1.2.317 → 1.2.318

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.
@@ -1,25 +1,3 @@
1
- /**
2
- * Detection demand per inference device, for the Best settings recommender
3
- * (D745). Pure.
4
- *
5
- * Demand is inferences per second a device must sustain:
6
- *
7
- * - a camera PINNED to a device charges its full rate to that device;
8
- * - an ALWAYS-ON camera charges its rate to its standing node, split over
9
- * that node's eligible devices by balancer weight (the device tier rotates
10
- * ties, so the weight share is the long-run split);
11
- * - ON-MOTION cameras are placed per session anywhere in the cluster, so
12
- * they are charged by how many sessions a device has really carried at
13
- * once: the observed 24 h peak of attached sessions on that device, minus
14
- * the always-on cameras it already counts, filled with the highest
15
- * on-motion rates. A device observed for less than 24 h (the hub
16
- * restarted) falls back to a fixed estimate — a quarter of the on-motion
17
- * cameras, at least two, spread evenly over the nodes and by weight over a
18
- * node's devices — and the plan says so.
19
- *
20
- * Every device must also fit the single fastest camera that can land on it:
21
- * one session cannot be split across devices.
22
- */
23
1
  import type { DeviceDemand } from './plan-schemas.js';
24
2
  /** How a camera's detection runs. `disabled` cameras charge nothing. */
25
3
  export type BestSettingsDetectionMode = 'always-on' | 'on-motion' | 'disabled';
@@ -33,12 +11,29 @@ export interface BestSettingsCameraLoad {
33
11
  readonly runsPipelineRoot: boolean;
34
12
  /** The node an always-on camera stands on; null when not placed (on-motion). */
35
13
  readonly standingNodeId: string | null;
36
- /** An operator pin of this camera to one `(node, device)`. */
14
+ /** The operator's node pin (`pipelineNodeId`), or null. */
15
+ readonly nodePinId: string | null;
16
+ /**
17
+ * An operator pin of this camera to one device. `nodeId` null = the pin
18
+ * names only a deviceKey (no node pin, no standing node): it resolves to the
19
+ * one node that has that key, or is ignored when several or none do.
20
+ */
37
21
  readonly devicePin: {
38
- readonly nodeId: string;
22
+ readonly nodeId: string | null;
39
23
  readonly deviceKey: string;
40
24
  } | null;
41
25
  }
26
+ /** The cluster-wide peak of parallel sessions (sum over every device at one census). */
27
+ export interface ObservedClusterPeak {
28
+ readonly peakSessions: number;
29
+ readonly observedForMs: number;
30
+ }
31
+ /** Share placement input: node shares and the cluster-wide peak. */
32
+ export interface DemandShares {
33
+ /** nodeId → share (any scale). Device shares are the eligible devices' weights. */
34
+ readonly nodes: Readonly<Record<string, number>>;
35
+ readonly clusterPeak: ObservedClusterPeak | null;
36
+ }
42
37
  /** A device the balancer may place a session on. */
43
38
  export interface BestSettingsEligibleDevice {
44
39
  readonly nodeId: string;
@@ -75,4 +70,6 @@ export declare function estimateDeviceDemand(input: {
75
70
  readonly eligible: readonly BestSettingsEligibleDevice[];
76
71
  readonly peaks: readonly ObservedDevicePeak[];
77
72
  readonly options?: DemandOptions;
73
+ /** Present ⇒ share placement (D748); absent ⇒ least-loaded. */
74
+ readonly shares?: DemandShares;
78
75
  }): readonly DeviceDemand[];
@@ -86,6 +86,10 @@ export declare const DeviceDemandSchema: z.ZodObject<{
86
86
  estimate: "estimate";
87
87
  }>;
88
88
  observedForMs: z.ZodNullable<z.ZodNumber>;
89
+ placement: z.ZodEnum<{
90
+ "least-loaded": "least-loaded";
91
+ share: "share";
92
+ }>;
89
93
  }, z.core.$strip>;
90
94
  export declare const DeviceModelChoiceSchema: z.ZodObject<{
91
95
  modelId: z.ZodString;
@@ -120,6 +124,10 @@ export declare const DeviceRecommendationSchema: z.ZodObject<{
120
124
  estimate: "estimate";
121
125
  }>;
122
126
  observedForMs: z.ZodNullable<z.ZodNumber>;
127
+ placement: z.ZodEnum<{
128
+ "least-loaded": "least-loaded";
129
+ share: "share";
130
+ }>;
123
131
  }, z.core.$strip>>;
124
132
  chosen: z.ZodNullable<z.ZodObject<{
125
133
  modelId: z.ZodString;
@@ -221,6 +229,10 @@ export declare const NodeRecommendationSchema: z.ZodObject<{
221
229
  estimate: "estimate";
222
230
  }>;
223
231
  observedForMs: z.ZodNullable<z.ZodNumber>;
232
+ placement: z.ZodEnum<{
233
+ "least-loaded": "least-loaded";
234
+ share: "share";
235
+ }>;
224
236
  }, z.core.$strip>>;
225
237
  chosen: z.ZodNullable<z.ZodObject<{
226
238
  modelId: z.ZodString;
@@ -386,11 +398,16 @@ export declare const BestSettingsWarningSchema: z.ZodDiscriminatedUnion<[z.ZodOb
386
398
  }, z.core.$strip>], "kind">;
387
399
  export declare const BestSettingsOptionsSchema: z.ZodObject<{
388
400
  headroom: z.ZodDefault<z.ZodNumber>;
401
+ shares: z.ZodOptional<z.ZodObject<{
402
+ nodes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
403
+ devices: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodNumber>>>;
404
+ }, z.core.$strip>>;
389
405
  }, z.core.$strip>;
390
406
  export declare const BestSettingsPlanSchema: z.ZodObject<{
391
407
  planHash: z.ZodString;
392
408
  family: z.ZodLiteral<"yolov9">;
393
409
  headroom: z.ZodNumber;
410
+ whatIf: z.ZodBoolean;
394
411
  generatedAt: z.ZodNumber;
395
412
  nodes: z.ZodReadonly<z.ZodArray<z.ZodObject<{
396
413
  nodeId: z.ZodString;
@@ -430,6 +447,10 @@ export declare const BestSettingsPlanSchema: z.ZodObject<{
430
447
  estimate: "estimate";
431
448
  }>;
432
449
  observedForMs: z.ZodNullable<z.ZodNumber>;
450
+ placement: z.ZodEnum<{
451
+ "least-loaded": "least-loaded";
452
+ share: "share";
453
+ }>;
433
454
  }, z.core.$strip>>;
434
455
  chosen: z.ZodNullable<z.ZodObject<{
435
456
  modelId: z.ZodString;
@@ -637,6 +658,7 @@ export declare const BestSettingsApplyResultSchema: z.ZodDiscriminatedUnion<[z.Z
637
658
  planHash: z.ZodString;
638
659
  family: z.ZodLiteral<"yolov9">;
639
660
  headroom: z.ZodNumber;
661
+ whatIf: z.ZodBoolean;
640
662
  generatedAt: z.ZodNumber;
641
663
  nodes: z.ZodReadonly<z.ZodArray<z.ZodObject<{
642
664
  nodeId: z.ZodString;
@@ -676,6 +698,10 @@ export declare const BestSettingsApplyResultSchema: z.ZodDiscriminatedUnion<[z.Z
676
698
  estimate: "estimate";
677
699
  }>;
678
700
  observedForMs: z.ZodNullable<z.ZodNumber>;
701
+ placement: z.ZodEnum<{
702
+ "least-loaded": "least-loaded";
703
+ share: "share";
704
+ }>;
679
705
  }, z.core.$strip>>;
680
706
  chosen: z.ZodNullable<z.ZodObject<{
681
707
  modelId: z.ZodString;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Load balancing (D748): the share placement policy for detection sessions
3
+ * and the shapes of its admin surface. See `share-election.ts`.
4
+ */
5
+ export * from './share-election.js';
6
+ export * from './schemas.js';
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Wire shapes of the Load balancing surface (D748): the session placement
3
+ * policy, the atomic share write, the observed placement statistics, the
4
+ * hardware share suggestion and the stale device pins.
5
+ */
6
+ import { z } from 'zod';
7
+ /**
8
+ * How a new detection session picks its node and device.
9
+ * - `least-loaded` — the historical rule: lowest `attached / weight`, a
10
+ * constant node order at a tie (D37). An idle node always ties at 0, so a
11
+ * weight only bites once a node already holds a session.
12
+ * - `share` — the share of PARALLEL sessions is held to the weights, and a
13
+ * full or drowning node yields to a free one (D748).
14
+ */
15
+ export declare const SessionPlacementPolicySchema: z.ZodEnum<{
16
+ "least-loaded": "least-loaded";
17
+ share: "share";
18
+ }>;
19
+ export type SessionPlacementPolicy = z.infer<typeof SessionPlacementPolicySchema>;
20
+ export declare const DEFAULT_SESSION_PLACEMENT_POLICY: SessionPlacementPolicy;
21
+ /** The global-settings key the policy is stored under (orchestrator `load` section). */
22
+ export declare const SESSION_PLACEMENT_SETTING_KEY = "sessionPlacement";
23
+ /** Parse a stored value; anything unknown is the default (nothing changes on deploy). */
24
+ export declare function parseSessionPlacementPolicy(value: unknown): SessionPlacementPolicy;
25
+ export declare const LoadBalancingSharesInputSchema: z.ZodObject<{
26
+ nodes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
27
+ devices: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodNumber>>>;
28
+ }, z.core.$strip>;
29
+ export type LoadBalancingSharesInput = z.infer<typeof LoadBalancingSharesInputSchema>;
30
+ export declare const PlacementStatsInputSchema: z.ZodObject<{
31
+ windowMs: z.ZodDefault<z.ZodNumber>;
32
+ }, z.core.$strip>;
33
+ export type PlacementStatsInput = z.infer<typeof PlacementStatsInputSchema>;
34
+ export declare const PlacementDeviceStatsSchema: z.ZodObject<{
35
+ deviceKey: z.ZodString;
36
+ sessionsNow: z.ZodNumber;
37
+ sessionsStarted: z.ZodNumber;
38
+ observedShare: z.ZodNullable<z.ZodNumber>;
39
+ }, z.core.$strip>;
40
+ export type PlacementDeviceStats = z.infer<typeof PlacementDeviceStatsSchema>;
41
+ export declare const PlacementNodeStatsSchema: z.ZodObject<{
42
+ nodeId: z.ZodString;
43
+ sessionsNow: z.ZodNumber;
44
+ sessionsStarted: z.ZodNumber;
45
+ observedShare: z.ZodNullable<z.ZodNumber>;
46
+ peakSessions: z.ZodNumber;
47
+ yieldsAway: z.ZodNumber;
48
+ devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
49
+ deviceKey: z.ZodString;
50
+ sessionsNow: z.ZodNumber;
51
+ sessionsStarted: z.ZodNumber;
52
+ observedShare: z.ZodNullable<z.ZodNumber>;
53
+ }, z.core.$strip>>>;
54
+ }, z.core.$strip>;
55
+ export type PlacementNodeStats = z.infer<typeof PlacementNodeStatsSchema>;
56
+ /** A camera's device pin that the session path cannot honour. */
57
+ export declare const StaleDevicePinSchema: z.ZodObject<{
58
+ deviceId: z.ZodNumber;
59
+ deviceKey: z.ZodString;
60
+ reason: z.ZodEnum<{
61
+ absent: "absent";
62
+ disabled: "disabled";
63
+ }>;
64
+ }, z.core.$strip>;
65
+ export type StaleDevicePin = z.infer<typeof StaleDevicePinSchema>;
66
+ export declare const PlacementStatsSchema: z.ZodObject<{
67
+ policy: z.ZodEnum<{
68
+ "least-loaded": "least-loaded";
69
+ share: "share";
70
+ }>;
71
+ windowMs: z.ZodNumber;
72
+ observedForMs: z.ZodNumber;
73
+ sessionsNow: z.ZodNumber;
74
+ clusterPeakSessions: z.ZodNumber;
75
+ nodes: z.ZodReadonly<z.ZodArray<z.ZodObject<{
76
+ nodeId: z.ZodString;
77
+ sessionsNow: z.ZodNumber;
78
+ sessionsStarted: z.ZodNumber;
79
+ observedShare: z.ZodNullable<z.ZodNumber>;
80
+ peakSessions: z.ZodNumber;
81
+ yieldsAway: z.ZodNumber;
82
+ devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
83
+ deviceKey: z.ZodString;
84
+ sessionsNow: z.ZodNumber;
85
+ sessionsStarted: z.ZodNumber;
86
+ observedShare: z.ZodNullable<z.ZodNumber>;
87
+ }, z.core.$strip>>>;
88
+ }, z.core.$strip>>>;
89
+ stalePins: z.ZodReadonly<z.ZodArray<z.ZodObject<{
90
+ deviceId: z.ZodNumber;
91
+ deviceKey: z.ZodString;
92
+ reason: z.ZodEnum<{
93
+ absent: "absent";
94
+ disabled: "disabled";
95
+ }>;
96
+ }, z.core.$strip>>>;
97
+ }, z.core.$strip>;
98
+ export type PlacementStats = z.infer<typeof PlacementStatsSchema>;
99
+ export declare const SuggestSharesInputSchema: z.ZodObject<{
100
+ headroom: z.ZodDefault<z.ZodNumber>;
101
+ }, z.core.$strip>;
102
+ export type SuggestSharesInput = z.infer<typeof SuggestSharesInputSchema>;
103
+ export declare const SuggestedDeviceShareSchema: z.ZodObject<{
104
+ deviceKey: z.ZodString;
105
+ share: z.ZodNumber;
106
+ modelId: z.ZodNullable<z.ZodString>;
107
+ fps: z.ZodNullable<z.ZodNumber>;
108
+ measured: z.ZodBoolean;
109
+ suggestedMaxSessions: z.ZodNullable<z.ZodNumber>;
110
+ }, z.core.$strip>;
111
+ export type SuggestedDeviceShare = z.infer<typeof SuggestedDeviceShareSchema>;
112
+ export declare const SuggestedNodeShareSchema: z.ZodObject<{
113
+ nodeId: z.ZodString;
114
+ share: z.ZodNullable<z.ZodNumber>;
115
+ capacitySessions: z.ZodNumber;
116
+ devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
117
+ deviceKey: z.ZodString;
118
+ share: z.ZodNumber;
119
+ modelId: z.ZodNullable<z.ZodString>;
120
+ fps: z.ZodNullable<z.ZodNumber>;
121
+ measured: z.ZodBoolean;
122
+ suggestedMaxSessions: z.ZodNullable<z.ZodNumber>;
123
+ }, z.core.$strip>>>;
124
+ skippedReason: z.ZodNullable<z.ZodString>;
125
+ }, z.core.$strip>;
126
+ export type SuggestedNodeShare = z.infer<typeof SuggestedNodeShareSchema>;
127
+ export declare const SuggestSharesResultSchema: z.ZodObject<{
128
+ headroom: z.ZodNumber;
129
+ cameraFps: z.ZodNumber;
130
+ iterations: z.ZodNumber;
131
+ nodes: z.ZodReadonly<z.ZodArray<z.ZodObject<{
132
+ nodeId: z.ZodString;
133
+ share: z.ZodNullable<z.ZodNumber>;
134
+ capacitySessions: z.ZodNumber;
135
+ devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
136
+ deviceKey: z.ZodString;
137
+ share: z.ZodNumber;
138
+ modelId: z.ZodNullable<z.ZodString>;
139
+ fps: z.ZodNullable<z.ZodNumber>;
140
+ measured: z.ZodBoolean;
141
+ suggestedMaxSessions: z.ZodNullable<z.ZodNumber>;
142
+ }, z.core.$strip>>>;
143
+ skippedReason: z.ZodNullable<z.ZodString>;
144
+ }, z.core.$strip>>>;
145
+ }, z.core.$strip>;
146
+ export type SuggestSharesResult = z.infer<typeof SuggestSharesResultSchema>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Share placement (D748): which node — or which device of a node — a NEW
3
+ * detection session goes to when the cluster's `sessionPlacement` policy is
4
+ * `share`. Pure.
5
+ *
6
+ * A share is a share of PARALLEL sessions. At each session start the
7
+ * candidate furthest below its share of the sessions running once this one
8
+ * starts wins:
9
+ *
10
+ * deficit_i = share_i / Σshare × (Σrunning + 1) − running_i
11
+ *
12
+ * so with 70/20/10 and an empty cluster the first session goes to the 70,
13
+ * and ten concurrent sessions settle at 7/2/1. Sessions a pin placed count
14
+ * toward their candidate like any other — a pinned node is not owed extra.
15
+ *
16
+ * Work-conserving: when the turn holder has no room — at an EXPLICIT cap
17
+ * (`maxSessions` / `maxCameras`) or drowning (its pool at the shed cap) — the
18
+ * session goes to the free candidate with the most spare explicit capacity,
19
+ * then the largest deficit. Never onto a saturated one. A capacity derived
20
+ * from the measured latency table is advisory only and never enters here: it
21
+ * would turn a suggestion into a de-facto cap.
22
+ *
23
+ * When every uncapped candidate is saturated the turn holder keeps the
24
+ * session: excluding the whole set would silently disable placement.
25
+ */
26
+ export interface ShareCandidate {
27
+ readonly key: string;
28
+ /** Relative share (any scale; ≤ 0 = wanted last). */
29
+ readonly share: number;
30
+ /** Sessions running on this candidate now. */
31
+ readonly running: number;
32
+ /** At an explicit cap — never elected. */
33
+ readonly capped: boolean;
34
+ /** Drowning (pool queue at its shed cap) — stepped over while another is free. */
35
+ readonly saturated: boolean;
36
+ /** Spare sessions under an explicit cap; absent = no explicit cap. */
37
+ readonly spare?: number;
38
+ }
39
+ export type ShareYieldReason = 'at-cap' | 'saturated';
40
+ export type ShareElection = {
41
+ readonly kind: 'elected';
42
+ readonly key: string;
43
+ readonly reason: 'share';
44
+ } | {
45
+ readonly kind: 'elected';
46
+ readonly key: string;
47
+ readonly reason: 'yield';
48
+ /** The candidate whose turn it was. */
49
+ readonly turnKey: string;
50
+ readonly yieldReason: ShareYieldReason;
51
+ } | {
52
+ readonly kind: 'none';
53
+ };
54
+ export declare function electByShare(candidates: readonly ShareCandidate[]): ShareElection;
@@ -157,7 +157,6 @@ export interface AgentPipelineSettings {
157
157
  * resolved tree.
158
158
  */
159
159
  export interface CameraPipelineSettings {
160
- readonly pinnedAgentNodeId?: string;
161
160
  readonly stepToggles?: Readonly<Record<string, boolean>>;
162
161
  /**
163
162
  * Per-(camera,node,device) model/settings override. Addressed by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.317",
3
+ "version": "1.2.318",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",