@moldesk/registry 0.0.5 → 0.0.6
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/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/schema.d.ts +171 -0
- package/dist/schema.js +75 -0
- package/dist/schema.test.js +58 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { MoldeskError } from "./errors.js";
|
|
2
2
|
export type { MoldeskErrorData } from "./errors.js";
|
|
3
|
-
export { allowedCommandTokens, commandSpecSchema, modelManifestV1Schema, } from "./schema.js";
|
|
4
|
-
export type { AssetSpec, CommandSpec, HardwareRequirements, InputSpec, ModelCategory, ModelManifestV1, ModelStatus, OutputSpec, PlatformId, PostInstallHook, PythonRequirement, PythonRuntimeSpec, DockerRuntimeSpec, RequirementLevel, RuntimeKind, RuntimeSpec, SourceSpec, } from "./schema.js";
|
|
3
|
+
export { allowedCommandTokens, commandSpecSchema, isCloudEligible, modelManifestV1Schema, } from "./schema.js";
|
|
4
|
+
export type { AssetSpec, CloudSpec, CommandSpec, HardwareRequirements, InputSpec, ModelCategory, ModelManifestV1, ModelStatus, OutputSpec, PlatformId, PostInstallHook, PythonRequirement, PythonRuntimeSpec, DockerRuntimeSpec, RequirementLevel, RuntimeKind, RuntimeSpec, SourceSpec, } from "./schema.js";
|
|
5
5
|
export type { LegacyModelManifest } from "./types.js";
|
|
6
6
|
export { getModelsDir, listAvailableModels, loadManifestFile, parseManifest, } from "./loader.js";
|
|
7
7
|
export { detectHostPlatformId, effectiveRuntimeSource, selectPythonRuntime, } from "./runtime-selection.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { MoldeskError } from "./errors.js";
|
|
2
|
-
export { allowedCommandTokens, commandSpecSchema, modelManifestV1Schema, } from "./schema.js";
|
|
2
|
+
export { allowedCommandTokens, commandSpecSchema, isCloudEligible, modelManifestV1Schema, } from "./schema.js";
|
|
3
3
|
export { getModelsDir, listAvailableModels, loadManifestFile, parseManifest, } from "./loader.js";
|
|
4
4
|
export { detectHostPlatformId, effectiveRuntimeSource, selectPythonRuntime, } from "./runtime-selection.js";
|
package/dist/schema.d.ts
CHANGED
|
@@ -463,6 +463,64 @@ declare const assetSchema: z.ZodObject<{
|
|
|
463
463
|
sizeBytes?: number | undefined;
|
|
464
464
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
465
465
|
}>;
|
|
466
|
+
/**
|
|
467
|
+
* Opt-in remote execution capability. Versioned independently of the manifest so the cloud contract
|
|
468
|
+
* can evolve without a manifest schema bump. Every value here must come from a live validation of
|
|
469
|
+
* that model on that image; a manifest without this block is never run in the cloud. Reuses the
|
|
470
|
+
* manifest's own `assets`, `runtimes` and `source`; it adds only what the cloud path cannot derive.
|
|
471
|
+
*/
|
|
472
|
+
declare const cloudSpecSchema: z.ZodObject<{
|
|
473
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
474
|
+
platform: z.ZodLiteral<"linux-x64">;
|
|
475
|
+
images: z.ZodObject<{
|
|
476
|
+
/** Generic CUDA worker image used for inference. */
|
|
477
|
+
gpu: z.ZodString;
|
|
478
|
+
/** Generic CPU worker image used only to prepare assets on a network volume. */
|
|
479
|
+
cpu: z.ZodString;
|
|
480
|
+
}, "strict", z.ZodTypeAny, {
|
|
481
|
+
cpu: string;
|
|
482
|
+
gpu: string;
|
|
483
|
+
}, {
|
|
484
|
+
cpu: string;
|
|
485
|
+
gpu: string;
|
|
486
|
+
}>;
|
|
487
|
+
minVramGb: z.ZodNumber;
|
|
488
|
+
minRamGb: z.ZodNumber;
|
|
489
|
+
/** Container disk for one ephemeral job: runtime environment, inputs and outputs (and assets when ephemeral). */
|
|
490
|
+
ephemeralDiskGb: z.ZodNumber;
|
|
491
|
+
/** Persistent network volume holding the raw object cache plus materialized assets. */
|
|
492
|
+
volumeGb: z.ZodNumber;
|
|
493
|
+
/** Bump when the on-volume asset layout changes; invalidates persistent caches. */
|
|
494
|
+
assetLayoutRevision: z.ZodString;
|
|
495
|
+
/** True when assets can be prepared on a CPU-only Pod without Python or CUDA. */
|
|
496
|
+
cpuAssetPreparation: z.ZodBoolean;
|
|
497
|
+
}, "strict", z.ZodTypeAny, {
|
|
498
|
+
minVramGb: number;
|
|
499
|
+
minRamGb: number;
|
|
500
|
+
schemaVersion: 1;
|
|
501
|
+
platform: "linux-x64";
|
|
502
|
+
images: {
|
|
503
|
+
cpu: string;
|
|
504
|
+
gpu: string;
|
|
505
|
+
};
|
|
506
|
+
ephemeralDiskGb: number;
|
|
507
|
+
volumeGb: number;
|
|
508
|
+
assetLayoutRevision: string;
|
|
509
|
+
cpuAssetPreparation: boolean;
|
|
510
|
+
}, {
|
|
511
|
+
minVramGb: number;
|
|
512
|
+
minRamGb: number;
|
|
513
|
+
schemaVersion: 1;
|
|
514
|
+
platform: "linux-x64";
|
|
515
|
+
images: {
|
|
516
|
+
cpu: string;
|
|
517
|
+
gpu: string;
|
|
518
|
+
};
|
|
519
|
+
ephemeralDiskGb: number;
|
|
520
|
+
volumeGb: number;
|
|
521
|
+
assetLayoutRevision: string;
|
|
522
|
+
cpuAssetPreparation: boolean;
|
|
523
|
+
}>;
|
|
466
524
|
declare const inputSpecSchema: z.ZodObject<{
|
|
467
525
|
formats: z.ZodArray<z.ZodString, "many">;
|
|
468
526
|
required: z.ZodBoolean;
|
|
@@ -786,6 +844,58 @@ export declare const modelManifestV1Schema: z.ZodEffects<z.ZodObject<{
|
|
|
786
844
|
sizeBytes?: number | undefined;
|
|
787
845
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
788
846
|
}>, "many">>;
|
|
847
|
+
cloud: z.ZodOptional<z.ZodObject<{
|
|
848
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
849
|
+
platform: z.ZodLiteral<"linux-x64">;
|
|
850
|
+
images: z.ZodObject<{
|
|
851
|
+
/** Generic CUDA worker image used for inference. */
|
|
852
|
+
gpu: z.ZodString;
|
|
853
|
+
/** Generic CPU worker image used only to prepare assets on a network volume. */
|
|
854
|
+
cpu: z.ZodString;
|
|
855
|
+
}, "strict", z.ZodTypeAny, {
|
|
856
|
+
cpu: string;
|
|
857
|
+
gpu: string;
|
|
858
|
+
}, {
|
|
859
|
+
cpu: string;
|
|
860
|
+
gpu: string;
|
|
861
|
+
}>;
|
|
862
|
+
minVramGb: z.ZodNumber;
|
|
863
|
+
minRamGb: z.ZodNumber;
|
|
864
|
+
/** Container disk for one ephemeral job: runtime environment, inputs and outputs (and assets when ephemeral). */
|
|
865
|
+
ephemeralDiskGb: z.ZodNumber;
|
|
866
|
+
/** Persistent network volume holding the raw object cache plus materialized assets. */
|
|
867
|
+
volumeGb: z.ZodNumber;
|
|
868
|
+
/** Bump when the on-volume asset layout changes; invalidates persistent caches. */
|
|
869
|
+
assetLayoutRevision: z.ZodString;
|
|
870
|
+
/** True when assets can be prepared on a CPU-only Pod without Python or CUDA. */
|
|
871
|
+
cpuAssetPreparation: z.ZodBoolean;
|
|
872
|
+
}, "strict", z.ZodTypeAny, {
|
|
873
|
+
minVramGb: number;
|
|
874
|
+
minRamGb: number;
|
|
875
|
+
schemaVersion: 1;
|
|
876
|
+
platform: "linux-x64";
|
|
877
|
+
images: {
|
|
878
|
+
cpu: string;
|
|
879
|
+
gpu: string;
|
|
880
|
+
};
|
|
881
|
+
ephemeralDiskGb: number;
|
|
882
|
+
volumeGb: number;
|
|
883
|
+
assetLayoutRevision: string;
|
|
884
|
+
cpuAssetPreparation: boolean;
|
|
885
|
+
}, {
|
|
886
|
+
minVramGb: number;
|
|
887
|
+
minRamGb: number;
|
|
888
|
+
schemaVersion: 1;
|
|
889
|
+
platform: "linux-x64";
|
|
890
|
+
images: {
|
|
891
|
+
cpu: string;
|
|
892
|
+
gpu: string;
|
|
893
|
+
};
|
|
894
|
+
ephemeralDiskGb: number;
|
|
895
|
+
volumeGb: number;
|
|
896
|
+
assetLayoutRevision: string;
|
|
897
|
+
cpuAssetPreparation: boolean;
|
|
898
|
+
}>>;
|
|
789
899
|
input: z.ZodObject<{
|
|
790
900
|
formats: z.ZodArray<z.ZodString, "many">;
|
|
791
901
|
required: z.ZodBoolean;
|
|
@@ -914,6 +1024,20 @@ export declare const modelManifestV1Schema: z.ZodEffects<z.ZodObject<{
|
|
|
914
1024
|
sizeBytes?: number | undefined;
|
|
915
1025
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
916
1026
|
}[] | undefined;
|
|
1027
|
+
cloud?: {
|
|
1028
|
+
minVramGb: number;
|
|
1029
|
+
minRamGb: number;
|
|
1030
|
+
schemaVersion: 1;
|
|
1031
|
+
platform: "linux-x64";
|
|
1032
|
+
images: {
|
|
1033
|
+
cpu: string;
|
|
1034
|
+
gpu: string;
|
|
1035
|
+
};
|
|
1036
|
+
ephemeralDiskGb: number;
|
|
1037
|
+
volumeGb: number;
|
|
1038
|
+
assetLayoutRevision: string;
|
|
1039
|
+
cpuAssetPreparation: boolean;
|
|
1040
|
+
} | undefined;
|
|
917
1041
|
}, {
|
|
918
1042
|
status: "available" | "beta" | "planned";
|
|
919
1043
|
name: string;
|
|
@@ -1011,6 +1135,20 @@ export declare const modelManifestV1Schema: z.ZodEffects<z.ZodObject<{
|
|
|
1011
1135
|
sizeBytes?: number | undefined;
|
|
1012
1136
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
1013
1137
|
}[] | undefined;
|
|
1138
|
+
cloud?: {
|
|
1139
|
+
minVramGb: number;
|
|
1140
|
+
minRamGb: number;
|
|
1141
|
+
schemaVersion: 1;
|
|
1142
|
+
platform: "linux-x64";
|
|
1143
|
+
images: {
|
|
1144
|
+
cpu: string;
|
|
1145
|
+
gpu: string;
|
|
1146
|
+
};
|
|
1147
|
+
ephemeralDiskGb: number;
|
|
1148
|
+
volumeGb: number;
|
|
1149
|
+
assetLayoutRevision: string;
|
|
1150
|
+
cpuAssetPreparation: boolean;
|
|
1151
|
+
} | undefined;
|
|
1014
1152
|
}>, {
|
|
1015
1153
|
status: "available" | "beta" | "planned";
|
|
1016
1154
|
name: string;
|
|
@@ -1108,6 +1246,20 @@ export declare const modelManifestV1Schema: z.ZodEffects<z.ZodObject<{
|
|
|
1108
1246
|
sizeBytes?: number | undefined;
|
|
1109
1247
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
1110
1248
|
}[] | undefined;
|
|
1249
|
+
cloud?: {
|
|
1250
|
+
minVramGb: number;
|
|
1251
|
+
minRamGb: number;
|
|
1252
|
+
schemaVersion: 1;
|
|
1253
|
+
platform: "linux-x64";
|
|
1254
|
+
images: {
|
|
1255
|
+
cpu: string;
|
|
1256
|
+
gpu: string;
|
|
1257
|
+
};
|
|
1258
|
+
ephemeralDiskGb: number;
|
|
1259
|
+
volumeGb: number;
|
|
1260
|
+
assetLayoutRevision: string;
|
|
1261
|
+
cpuAssetPreparation: boolean;
|
|
1262
|
+
} | undefined;
|
|
1111
1263
|
}, {
|
|
1112
1264
|
status: "available" | "beta" | "planned";
|
|
1113
1265
|
name: string;
|
|
@@ -1205,6 +1357,20 @@ export declare const modelManifestV1Schema: z.ZodEffects<z.ZodObject<{
|
|
|
1205
1357
|
sizeBytes?: number | undefined;
|
|
1206
1358
|
archive?: "none" | "tar.gz" | "tar" | "zip" | undefined;
|
|
1207
1359
|
}[] | undefined;
|
|
1360
|
+
cloud?: {
|
|
1361
|
+
minVramGb: number;
|
|
1362
|
+
minRamGb: number;
|
|
1363
|
+
schemaVersion: 1;
|
|
1364
|
+
platform: "linux-x64";
|
|
1365
|
+
images: {
|
|
1366
|
+
cpu: string;
|
|
1367
|
+
gpu: string;
|
|
1368
|
+
};
|
|
1369
|
+
ephemeralDiskGb: number;
|
|
1370
|
+
volumeGb: number;
|
|
1371
|
+
assetLayoutRevision: string;
|
|
1372
|
+
cpuAssetPreparation: boolean;
|
|
1373
|
+
} | undefined;
|
|
1208
1374
|
}>;
|
|
1209
1375
|
export type ModelStatus = z.infer<typeof modelStatus>;
|
|
1210
1376
|
export type RuntimeKind = z.infer<typeof runtimeKind>;
|
|
@@ -1218,9 +1384,14 @@ export type DockerRuntimeSpec = z.infer<typeof dockerRuntimeSchema>;
|
|
|
1218
1384
|
export type RuntimeSpec = z.infer<typeof runtimeSpecSchema>;
|
|
1219
1385
|
export type HardwareRequirements = z.infer<typeof hardwareSchema>;
|
|
1220
1386
|
export type AssetSpec = z.infer<typeof assetSchema>;
|
|
1387
|
+
export type CloudSpec = z.infer<typeof cloudSpecSchema>;
|
|
1221
1388
|
export type InputSpec = z.infer<typeof inputSpecSchema>;
|
|
1222
1389
|
export type OutputSpec = z.infer<typeof outputSpecSchema>;
|
|
1223
1390
|
export type SourceSpec = z.infer<typeof sourceSchema>;
|
|
1224
1391
|
export type ModelManifestV1 = z.infer<typeof modelManifestV1Schema>;
|
|
1225
1392
|
export type CommandSpec = z.infer<typeof commandSpecSchema>;
|
|
1393
|
+
/** A manifest is cloud-eligible only by declaring a `cloud` block; there is no implicit eligibility. */
|
|
1394
|
+
export declare function isCloudEligible(manifest: ModelManifestV1): manifest is ModelManifestV1 & {
|
|
1395
|
+
cloud: CloudSpec;
|
|
1396
|
+
};
|
|
1226
1397
|
export {};
|
package/dist/schema.js
CHANGED
|
@@ -183,6 +183,43 @@ const assetSchema = z
|
|
|
183
183
|
archive: z.enum(["none", "tar.gz", "tar", "zip"]).optional(),
|
|
184
184
|
})
|
|
185
185
|
.strict();
|
|
186
|
+
/** Registry/name reference pinned by digest (`ghcr.io/org/img@sha256:<64 hex>`); a bare tag is never accepted. */
|
|
187
|
+
const digestPinnedImage = z
|
|
188
|
+
.string()
|
|
189
|
+
.regex(/^[a-z0-9]+(?:[._-][a-z0-9]+)*(?::\d+)?(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)+@sha256:[0-9a-f]{64}$/, "must be a lowercase image reference pinned by digest, e.g. registry.example/org/image@sha256:<64 hex>");
|
|
190
|
+
const cloudAssetLayoutRevision = z
|
|
191
|
+
.string()
|
|
192
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/, "must be a short safe token, e.g. \"1\"");
|
|
193
|
+
/**
|
|
194
|
+
* Opt-in remote execution capability. Versioned independently of the manifest so the cloud contract
|
|
195
|
+
* can evolve without a manifest schema bump. Every value here must come from a live validation of
|
|
196
|
+
* that model on that image; a manifest without this block is never run in the cloud. Reuses the
|
|
197
|
+
* manifest's own `assets`, `runtimes` and `source`; it adds only what the cloud path cannot derive.
|
|
198
|
+
*/
|
|
199
|
+
const cloudSpecSchema = z
|
|
200
|
+
.object({
|
|
201
|
+
schemaVersion: z.literal(1),
|
|
202
|
+
platform: z.literal("linux-x64"),
|
|
203
|
+
images: z
|
|
204
|
+
.object({
|
|
205
|
+
/** Generic CUDA worker image used for inference. */
|
|
206
|
+
gpu: digestPinnedImage,
|
|
207
|
+
/** Generic CPU worker image used only to prepare assets on a network volume. */
|
|
208
|
+
cpu: digestPinnedImage,
|
|
209
|
+
})
|
|
210
|
+
.strict(),
|
|
211
|
+
minVramGb: z.number().positive(),
|
|
212
|
+
minRamGb: z.number().positive(),
|
|
213
|
+
/** Container disk for one ephemeral job: runtime environment, inputs and outputs (and assets when ephemeral). */
|
|
214
|
+
ephemeralDiskGb: z.number().int().positive(),
|
|
215
|
+
/** Persistent network volume holding the raw object cache plus materialized assets. */
|
|
216
|
+
volumeGb: z.number().int().positive(),
|
|
217
|
+
/** Bump when the on-volume asset layout changes; invalidates persistent caches. */
|
|
218
|
+
assetLayoutRevision: cloudAssetLayoutRevision,
|
|
219
|
+
/** True when assets can be prepared on a CPU-only Pod without Python or CUDA. */
|
|
220
|
+
cpuAssetPreparation: z.boolean(),
|
|
221
|
+
})
|
|
222
|
+
.strict();
|
|
186
223
|
const inputSpecSchema = z
|
|
187
224
|
.object({
|
|
188
225
|
formats: z.array(z.string().min(1)).min(1),
|
|
@@ -247,6 +284,7 @@ export const modelManifestV1Schema = z
|
|
|
247
284
|
runtimes: z.array(runtimeSpecSchema).min(1),
|
|
248
285
|
hardware: hardwareSchema,
|
|
249
286
|
assets: z.array(assetSchema).optional(),
|
|
287
|
+
cloud: cloudSpecSchema.optional(),
|
|
250
288
|
input: inputSpecSchema,
|
|
251
289
|
outputs: z.array(outputSpecSchema).min(1),
|
|
252
290
|
})
|
|
@@ -282,6 +320,39 @@ export const modelManifestV1Schema = z
|
|
|
282
320
|
}
|
|
283
321
|
}
|
|
284
322
|
}
|
|
323
|
+
if (val.cloud) {
|
|
324
|
+
const cloud = val.cloud;
|
|
325
|
+
const remoteRuntimes = val.runtimes.filter((runtime) => runtime.kind === "python" && (runtime.platforms === undefined || runtime.platforms.includes(cloud.platform)));
|
|
326
|
+
if (remoteRuntimes.length === 0) {
|
|
327
|
+
ctx.addIssue({
|
|
328
|
+
code: z.ZodIssueCode.custom,
|
|
329
|
+
message: `cloud requires a python runtime entry that applies to ${cloud.platform}`,
|
|
330
|
+
path: ["cloud", "platform"],
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
for (const runtime of remoteRuntimes) {
|
|
334
|
+
if (runtime.minVramGb !== undefined && cloud.minVramGb < runtime.minVramGb) {
|
|
335
|
+
ctx.addIssue({
|
|
336
|
+
code: z.ZodIssueCode.custom,
|
|
337
|
+
message: `cloud.minVramGb (${cloud.minVramGb}) must be at least the ${cloud.platform} runtime's minVramGb (${runtime.minVramGb})`,
|
|
338
|
+
path: ["cloud", "minVramGb"],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Lower bound only: compressed asset bytes alone must fit. Extraction and runtime headroom
|
|
343
|
+
// come from live measurement, not from this check.
|
|
344
|
+
const assetBytes = (val.assets ?? []).reduce((total, asset) => total + (asset.sizeBytes ?? 0), 0);
|
|
345
|
+
const GIB = 1024 ** 3;
|
|
346
|
+
for (const field of ["ephemeralDiskGb", "volumeGb"]) {
|
|
347
|
+
if (cloud[field] * GIB < assetBytes) {
|
|
348
|
+
ctx.addIssue({
|
|
349
|
+
code: z.ZodIssueCode.custom,
|
|
350
|
+
message: `cloud.${field} (${cloud[field]} GB) is smaller than the manifest's declared asset bytes (${(assetBytes / GIB).toFixed(2)} GiB)`,
|
|
351
|
+
path: ["cloud", field],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
285
356
|
const names = new Set();
|
|
286
357
|
for (const out of val.outputs) {
|
|
287
358
|
if (names.has(out.id)) {
|
|
@@ -335,3 +406,7 @@ export const modelManifestV1Schema = z
|
|
|
335
406
|
}
|
|
336
407
|
}
|
|
337
408
|
});
|
|
409
|
+
/** A manifest is cloud-eligible only by declaring a `cloud` block; there is no implicit eligibility. */
|
|
410
|
+
export function isCloudEligible(manifest) {
|
|
411
|
+
return manifest.cloud !== undefined;
|
|
412
|
+
}
|
package/dist/schema.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { MoldeskError } from "./errors.js";
|
|
3
3
|
import { parseManifest } from "./loader.js";
|
|
4
|
-
import { commandSpecSchema } from "./schema.js";
|
|
4
|
+
import { commandSpecSchema, isCloudEligible } from "./schema.js";
|
|
5
5
|
function baseManifest() {
|
|
6
6
|
return {
|
|
7
7
|
schemaVersion: 1,
|
|
@@ -333,3 +333,60 @@ describe("parseManifest", () => {
|
|
|
333
333
|
}
|
|
334
334
|
});
|
|
335
335
|
});
|
|
336
|
+
describe("cloud capability block", () => {
|
|
337
|
+
const DIGEST = "a".repeat(64);
|
|
338
|
+
const cloud = () => ({
|
|
339
|
+
schemaVersion: 1,
|
|
340
|
+
platform: "linux-x64",
|
|
341
|
+
images: {
|
|
342
|
+
gpu: `registry.example/moldesk/worker-gpu@sha256:${DIGEST}`,
|
|
343
|
+
cpu: `registry.example/moldesk/worker-cpu@sha256:${DIGEST}`,
|
|
344
|
+
},
|
|
345
|
+
minVramGb: 24,
|
|
346
|
+
minRamGb: 32,
|
|
347
|
+
ephemeralDiskGb: 40,
|
|
348
|
+
volumeGb: 40,
|
|
349
|
+
assetLayoutRevision: "1",
|
|
350
|
+
cpuAssetPreparation: true,
|
|
351
|
+
});
|
|
352
|
+
const withCloud = (block, extra = {}) => ({ ...baseManifest(), ...extra, cloud: block });
|
|
353
|
+
it("accepts a complete block and marks the model eligible; absence means ineligible", () => {
|
|
354
|
+
const eligible = parseManifest(withCloud(cloud()), "x/manifest.yaml");
|
|
355
|
+
expect(isCloudEligible(eligible)).toBe(true);
|
|
356
|
+
expect(eligible.cloud?.images.gpu).toContain("@sha256:");
|
|
357
|
+
expect(isCloudEligible(parseManifest(baseManifest(), "x/manifest.yaml"))).toBe(false);
|
|
358
|
+
});
|
|
359
|
+
it("rejects tag-only or malformed image references", () => {
|
|
360
|
+
for (const gpu of ["registry.example/moldesk/worker:latest", "worker@sha256:" + DIGEST, `Registry.Example/x@sha256:${DIGEST}`, `registry.example/x@sha256:${"a".repeat(63)}`]) {
|
|
361
|
+
expect(() => parseManifest(withCloud({ ...cloud(), images: { ...cloud().images, gpu } }), "x/manifest.yaml")).toThrow(/pinned by digest/);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
it("is strict: unknown keys, other platforms and missing fields are rejected", () => {
|
|
365
|
+
expect(() => parseManifest(withCloud({ ...cloud(), command: "rm -rf /" }), "x/manifest.yaml")).toThrow();
|
|
366
|
+
expect(() => parseManifest(withCloud({ ...cloud(), platform: "darwin-arm64" }), "x/manifest.yaml")).toThrow();
|
|
367
|
+
const { volumeGb: _omit, ...missing } = cloud();
|
|
368
|
+
expect(() => parseManifest(withCloud(missing), "x/manifest.yaml")).toThrow();
|
|
369
|
+
expect(() => parseManifest(withCloud({ ...cloud(), minVramGb: 0 }), "x/manifest.yaml")).toThrow();
|
|
370
|
+
expect(() => parseManifest(withCloud({ ...cloud(), schemaVersion: 2 }), "x/manifest.yaml")).toThrow();
|
|
371
|
+
});
|
|
372
|
+
it("requires a python runtime entry that applies to linux-x64", () => {
|
|
373
|
+
const darwinOnly = {
|
|
374
|
+
runtimes: [{ kind: "python", python: "3.11", installer: "uv", platforms: ["darwin-arm64"], requirements: [{ name: "torch" }] }],
|
|
375
|
+
};
|
|
376
|
+
expect(() => parseManifest(withCloud(cloud(), darwinOnly), "x/manifest.yaml")).toThrow(/python runtime entry that applies to linux-x64/);
|
|
377
|
+
});
|
|
378
|
+
it("requires cloud.minVramGb to cover the linux runtime's own floor", () => {
|
|
379
|
+
const runtimes = [
|
|
380
|
+
{ kind: "python", python: "3.11", installer: "uv", platforms: ["linux-x64"], nvidiaGpu: "required", minVramGb: 40, requirements: [{ name: "torch" }] },
|
|
381
|
+
];
|
|
382
|
+
expect(() => parseManifest(withCloud(cloud(), { runtimes }), "x/manifest.yaml")).toThrow(/cloud\.minVramGb \(24\)/);
|
|
383
|
+
expect(parseManifest(withCloud({ ...cloud(), minVramGb: 40 }, { runtimes }), "x/manifest.yaml").cloud?.minVramGb).toBe(40);
|
|
384
|
+
});
|
|
385
|
+
it("rejects disk or volume sizes smaller than the declared asset bytes", () => {
|
|
386
|
+
const GIB = 1024 ** 3;
|
|
387
|
+
const assets = [{ id: "w", url: "https://example.com/w.bin", sha256: "b".repeat(64), target: "w.bin", sizeBytes: 50 * GIB }];
|
|
388
|
+
expect(() => parseManifest(withCloud({ ...cloud(), volumeGb: 60 }, { assets }), "x/manifest.yaml")).toThrow(/cloud\.ephemeralDiskGb \(40 GB\)/);
|
|
389
|
+
expect(() => parseManifest(withCloud({ ...cloud(), ephemeralDiskGb: 60 }, { assets }), "x/manifest.yaml")).toThrow(/cloud\.volumeGb \(40 GB\)/);
|
|
390
|
+
expect(parseManifest(withCloud({ ...cloud(), ephemeralDiskGb: 60, volumeGb: 60 }, { assets }), "x/manifest.yaml").cloud?.volumeGb).toBe(60);
|
|
391
|
+
});
|
|
392
|
+
});
|