@norskvideo/norsk-auto-manager 0.1.0

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.
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // Standard capability names shipped with Norsk. Use these constants
3
+ // rather than literal strings — typos in user code surface at compile
4
+ // time rather than as silent placement misses.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.KnownCapabilities = void 0;
7
+ /** @public */
8
+ exports.KnownCapabilities = {
9
+ /** Whole NVIDIA GPU, exclusive. For fractional, use requiredGpuCapacity. */
10
+ NvidiaGpu: "norsk.io/nvidia-gpu",
11
+ /** Intel Quick Sync Video — fixed-function media accelerator. */
12
+ IntelQsv: "norsk.io/intel-qsv",
13
+ /** Netint Quadra T1U accelerator. */
14
+ Quadra: "norsk.io/quadra",
15
+ /** Blackmagic DeckLink capture/playout card. */
16
+ Decklink: "norsk.io/decklink",
17
+ /** SMPTE ST 2110 capable NIC. */
18
+ St2110Nic: "norsk.io/st2110-nic",
19
+ /** Media eXchange Layer slot, provided by an MXL bridge NodeService. */
20
+ Mxl: "norsk.io/mxl",
21
+ };
22
+ //# sourceMappingURL=knownCapabilities.js.map
@@ -0,0 +1,268 @@
1
+ import { Bundle, BundleId, BundleJobSpec, Capability, NodeId, NodeInventory } from "./types";
2
+ /** @public */
3
+ export interface NodeView {
4
+ nodeId: NodeId;
5
+ poolName: string;
6
+ /**
7
+ * Which tier within the pool this node belongs to. Set from
8
+ * `tags["tier"]` at provision time for elastic tiers. Pre-registered
9
+ * cluster nodes carry no tier tag (undefined) and are treated as
10
+ * belonging to their pool's `fixed` tier — see `nodeMatchesTier`.
11
+ */
12
+ tierName?: string;
13
+ inventory: NodeInventory;
14
+ /** Jobs currently running on this node (or pending placement onto it). */
15
+ runningJobs: RunningJob[];
16
+ /** Topology key for the resilience sameAz rule. Cluster pools can leave undefined. */
17
+ az?: string;
18
+ /**
19
+ * Cloud / failure-domain key for the resilience sameCloud rule — the
20
+ * node's provider (e.g. "aws" / "oci" / "cluster"). Undefined when
21
+ * unknown; sameCloud then can't be evaluated against this node.
22
+ */
23
+ cloud?: string;
24
+ /** Hot spare flag (Phase C). Currently unused by hard-constraint filters. */
25
+ isHotSpare: boolean;
26
+ }
27
+ /** @public */
28
+ export interface RunningJob {
29
+ bundleId: BundleId;
30
+ replicaIndex: number;
31
+ jobName: string;
32
+ }
33
+ /**
34
+ * A pool is an ordered list of capacity tiers. Placement walks the tiers
35
+ * in order (cheapest first) and takes the first that can satisfy the job
36
+ * — the tier order *is* the cost cascade (no cost weight in v1). The
37
+ * homogeneous single-cloud pool of before is just a one-tier pool.
38
+ *
39
+ * @public
40
+ */
41
+ export interface PlacementPool {
42
+ name: string;
43
+ /** Ordered cheapest → most expensive. Placement tries them in order. */
44
+ tiers: PlacementTier[];
45
+ }
46
+ /**
47
+ * One homogeneous capacity segment within a pool: a single cloud/cluster
48
+ * kind + region with its own scale-out behaviour and candidate shapes.
49
+ *
50
+ * @public
51
+ */
52
+ export interface PlacementTier {
53
+ /** Unique within the pool. Tagged onto provisioned nodes as `tags["tier"]`. */
54
+ name: string;
55
+ /**
56
+ * Cloud / local-servers kind. Used by AutoManager to decide which gRPC
57
+ * call to make when provisioning (createAwsNode vs createOciNode vs
58
+ * local-servers-tier placement, which uses startJob against an existing
59
+ * registered server).
60
+ */
61
+ kind: "aws" | "oci" | "local-servers";
62
+ /**
63
+ * For aws/oci elastic tiers: which region/availability-domain to
64
+ * provision into. Cluster tiers leave undefined.
65
+ */
66
+ region?: string;
67
+ packingStrategy: "binpack" | "spread";
68
+ scaleOut: "elastic" | "fixed";
69
+ /**
70
+ * For elastic tiers: shapes that can be provisioned on demand. For
71
+ * fixed tiers: typically empty (the tier consists of registered
72
+ * cluster nodes already in `inventory`).
73
+ */
74
+ candidateInstanceTypes: InstanceTypeOption[];
75
+ /**
76
+ * How long ahead of a scheduled job's `startDateTime` AutoManager
77
+ * should kick off placement, so the worker is ready to run by the
78
+ * requested time. Cluster tiers place against an existing running
79
+ * node (~instant) so the default is 0. Cloud tiers have to boot a
80
+ * fresh instance — defaults to 5 minutes; operators set per-tier
81
+ * when their boot time differs. AutoManager picks the maximum across
82
+ * the bundle pool's tiers (worst case — guarantees readiness
83
+ * regardless of which tier the placement engine settles on).
84
+ */
85
+ placementLeadMs?: number;
86
+ /**
87
+ * How nodes in this tier are purchased. `"spot"` requests interruptible
88
+ * spot-market capacity; `"reserved"` is an on-demand launch the operator
89
+ * knows is covered by a standing reservation (same AWS call as on-demand,
90
+ * but capped — see `maxNodes`). Defaults to `"on-demand"`.
91
+ */
92
+ launchMode?: LaunchMode;
93
+ /**
94
+ * Failure-domain reliability of this tier's capacity. Defaults to
95
+ * `"interruptible"` for spot, `"durable"` otherwise. Read by the
96
+ * resilience engine — an interruptible-tier primary with no durable
97
+ * backup is flagged degraded.
98
+ */
99
+ reliability?: Reliability;
100
+ /**
101
+ * Cap on the number of nodes this tier may hold (tier-wide, across
102
+ * bundles). The cost cascade spills to the next tier once the cap is
103
+ * reached. Used mainly for reserved tiers — exceeding the reservation
104
+ * count just bills as on-demand, defeating the point. Unset ⇒ no cap.
105
+ */
106
+ maxNodes?: number;
107
+ }
108
+ /** @public */
109
+ export type LaunchMode = "on-demand" | "spot" | "reserved";
110
+ /** @public */
111
+ export type Reliability = "durable" | "interruptible";
112
+ /** The tier's launch mode, defaulting to on-demand. @public */
113
+ export declare function tierLaunchMode(tier: PlacementTier): LaunchMode;
114
+ /** The tier's reliability — explicit, else interruptible for spot. @public */
115
+ export declare function tierReliability(tier: PlacementTier): Reliability;
116
+ /** Default placement lead time by tier kind. Cluster tiers place
117
+ * against existing running nodes so we don't need lead. Cloud tiers
118
+ * default to 5 minutes — typical EC2/OCI instance boot + image pull
119
+ * budget; operators can override per-tier via PlacementTier. */
120
+ export declare function defaultPlacementLeadMs(kind: PlacementTier["kind"]): number;
121
+ /**
122
+ * Whether a node belongs to (pool, tier). Elastic nodes carry an explicit
123
+ * `tierName` (set from `tags["tier"]` at provision). Pre-registered cluster
124
+ * nodes carry no tier tag and are taken to belong to the pool's `fixed`
125
+ * tier — so a pool must have at most one fixed tier (enforced in
126
+ * settingsValidation) for this to be unambiguous.
127
+ *
128
+ * @public
129
+ */
130
+ export declare function nodeMatchesTier(n: NodeView, pool: PlacementPool, tier: PlacementTier): boolean;
131
+ /** @public */
132
+ export interface InstanceTypeOption {
133
+ instanceType: string;
134
+ totalCapacity: number;
135
+ totalCores: number;
136
+ capabilities: Capability[];
137
+ /** GPUs we could provision a node with — for feasibility check. */
138
+ gpus?: {
139
+ totalCapacity: number;
140
+ model?: string;
141
+ }[];
142
+ }
143
+ /** @public */
144
+ export interface RecoveryContext {
145
+ /** NodeIds the placement engine must skip (e.g., recently failed). */
146
+ excludeNodes: Set<NodeId>;
147
+ /** Reserved for Phase C — biases scoring toward hot spares. */
148
+ preferHotSpares: boolean;
149
+ }
150
+ /** @public */
151
+ export interface PlacementInput {
152
+ bundle: Bundle;
153
+ job: BundleJobSpec;
154
+ replicaIndex: number;
155
+ inventory: NodeView[];
156
+ pools: Map<string, PlacementPool>;
157
+ recoveryContext?: RecoveryContext;
158
+ }
159
+ /** @public */
160
+ /** Which anti-affinity flag(s) a backup placement couldn't honour. */
161
+ export type ResilienceFlag = "sameNode" | "sameAz" | "sameCloud";
162
+ /**
163
+ * Set on a backup placement that landed inside a failure domain its
164
+ * resilience policy asked to avoid (best-effort: no compliant node had
165
+ * capacity). The engine places it anyway and surfaces this so the
166
+ * operator knows the bundle's DR posture is currently reduced.
167
+ *
168
+ * @public
169
+ */
170
+ export interface ResilienceDegradation {
171
+ violated: ResilienceFlag[];
172
+ /**
173
+ * Set when the primary landed on interruptible (spot) capacity with no
174
+ * durable backup — a single interruption takes the workload down. Surfaced
175
+ * so the operator can add a durable backup.
176
+ */
177
+ interruptiblePrimary?: boolean;
178
+ }
179
+ /** @public */
180
+ export type PlacementResult = {
181
+ kind: "place";
182
+ nodeId: NodeId;
183
+ gpuIndex?: number;
184
+ pool: string;
185
+ tier: string;
186
+ degraded?: ResilienceDegradation;
187
+ } | {
188
+ kind: "provision";
189
+ pool: string;
190
+ tier: string;
191
+ instanceType: string;
192
+ launchMode: LaunchMode;
193
+ degraded?: ResilienceDegradation;
194
+ } | {
195
+ kind: "failure";
196
+ reason: PlacementFailureReason;
197
+ };
198
+ /** @public */
199
+ export interface PlacementFailureReason {
200
+ code: "noCapacityInAnyPool" | "noConfiguredPools";
201
+ triedPools: string[];
202
+ }
203
+ /**
204
+ * Structured trace of a placement decision — one entry per pool
205
+ * attempted, with per-node filter outcomes and resulting scores. Used
206
+ * for "why did my job land here?" debugging.
207
+ *
208
+ * @public
209
+ */
210
+ export interface PlacementTrace {
211
+ attempts: PoolAttempt[];
212
+ /** Final decision kind, mirroring PlacementResult.kind. */
213
+ decision: "place" | "provision" | "failure";
214
+ }
215
+ /** @public */
216
+ export interface PoolAttempt {
217
+ pool: string;
218
+ /** Which tier of the pool this attempt covers. */
219
+ tier: string;
220
+ /** Total nodes considered in this pool before any filtering. */
221
+ initialCandidates: number;
222
+ /** Why each non-matching node was filtered out. Indexed by nodeId. */
223
+ filtered: {
224
+ nodeId: NodeId;
225
+ reason: FilterReason;
226
+ }[];
227
+ /** Nodes that survived all filters, with their assigned scores. */
228
+ scored: {
229
+ nodeId: NodeId;
230
+ score: number;
231
+ }[];
232
+ /** What this pool attempt produced. */
233
+ outcome: {
234
+ kind: "placed";
235
+ nodeId: NodeId;
236
+ gpuIndex?: number;
237
+ } | {
238
+ kind: "provisioned";
239
+ instanceType: string;
240
+ } | {
241
+ kind: "exhausted";
242
+ } | {
243
+ kind: "no-instance-type";
244
+ } | {
245
+ kind: "missing-pool";
246
+ };
247
+ }
248
+ /** @public */
249
+ export type FilterReason = "wrong-pool" | "wrong-tier" | "excluded-recovery" | "unreachable" | "cordoned" | "missing-capability" | "insufficient-capacity" | "insufficient-cores" | "no-fitting-gpu" | "fails-intra-replica" | "fails-intra-az";
250
+ /**
251
+ * Core placement function. Pure — no side effects, no I/O.
252
+ *
253
+ * @public
254
+ */
255
+ export declare function place(input: PlacementInput): PlacementResult;
256
+ /**
257
+ * Placement with a structured trace of every pool attempted and every
258
+ * node's filter outcome. AutoManager logs the trace via debuglog when
259
+ * placing; tests use it to assert on the precise reason a candidate
260
+ * was rejected.
261
+ *
262
+ * @public
263
+ */
264
+ export declare function placeWithTrace(input: PlacementInput): {
265
+ result: PlacementResult;
266
+ trace: PlacementTrace;
267
+ };
268
+ //# sourceMappingURL=placement.d.ts.map