@norskvideo/ctl-product-template-schema 0.1.3 → 0.1.5

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/index.d.ts CHANGED
@@ -38,6 +38,10 @@ export declare const ProductTemplateAllocatedPortSchema: z.ZodObject<{
38
38
  label: z.ZodOptional<z.ZodString>;
39
39
  }, z.core.$strip>;
40
40
  export type ProductTemplateAllocatedPort = z.infer<typeof ProductTemplateAllocatedPortSchema>;
41
+ export declare const ProductTemplateSideloadSchema: z.ZodObject<{
42
+ gpuInference: z.ZodOptional<z.ZodBoolean>;
43
+ }, z.core.$strip>;
44
+ export type ProductTemplateSideload = z.infer<typeof ProductTemplateSideloadSchema>;
41
45
  export declare const ProductTemplateManifestSchema: z.ZodObject<{
42
46
  productTemplateSchemaVersion: z.ZodLiteral<1>;
43
47
  productName: z.ZodString;
@@ -64,6 +68,9 @@ export declare const ProductTemplateManifestSchema: z.ZodObject<{
64
68
  expose: z.ZodDefault<z.ZodArray<z.ZodString>>;
65
69
  }, z.core.$strip>>;
66
70
  requiresWorkingDirectory: z.ZodOptional<z.ZodBoolean>;
71
+ sideload: z.ZodOptional<z.ZodObject<{
72
+ gpuInference: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$strip>>;
67
74
  advanced: z.ZodOptional<z.ZodObject<{
68
75
  networkMode: z.ZodOptional<z.ZodObject<{
69
76
  default: z.ZodOptional<z.ZodEnum<{
@@ -173,6 +180,9 @@ export declare const ProductTemplateMaterialsSchema: z.ZodObject<{
173
180
  expose: z.ZodDefault<z.ZodArray<z.ZodString>>;
174
181
  }, z.core.$strip>>;
175
182
  requiresWorkingDirectory: z.ZodOptional<z.ZodBoolean>;
183
+ sideload: z.ZodOptional<z.ZodObject<{
184
+ gpuInference: z.ZodOptional<z.ZodBoolean>;
185
+ }, z.core.$strip>>;
176
186
  advanced: z.ZodOptional<z.ZodObject<{
177
187
  networkMode: z.ZodOptional<z.ZodObject<{
178
188
  default: z.ZodOptional<z.ZodEnum<{
package/index.js CHANGED
@@ -109,6 +109,18 @@ export const ProductTemplateAllocatedPortSchema = z
109
109
  label: z.string().optional().meta({ description: "Human label, e.g. 'MoQ preview (WebTransport)'" }),
110
110
  })
111
111
  .meta({ id: "ProductTemplateAllocatedPort", outputId: "ProductTemplateAllocatedPort" });
112
+ // CUDA sideload bundle needs (docker/SIDELOAD.md). Only `gpuInference` is
113
+ // declared: `gpu-video` (NVENC/NVDEC) follows the GPU reservation automatically,
114
+ // so it needs no declaration. The runner mounts the matching bundle only when
115
+ // the image is slimmed AND the GPU is reserved; otherwise this is inert (a stock
116
+ // image needs no bundle; inference without a bundle falls back to CPU).
117
+ export const ProductTemplateSideloadSchema = z
118
+ .object({
119
+ gpuInference: z.boolean().optional().meta({
120
+ description: "Deployment uses GPU-accelerated inference (ONNX NR-IQA/detection/VAD, GPU whisper). When the GPU is reserved on the host the runner mounts the gpu-inference CUDA bundle; otherwise inference falls back to CPU. gpu-video (NVENC/NVDEC) follows the GPU reservation automatically and needs no declaration.",
121
+ }),
122
+ })
123
+ .meta({ id: "ProductTemplateSideload", outputId: "ProductTemplateSideload" });
112
124
  export const ProductTemplateManifestSchema = z
113
125
  .object({
114
126
  productTemplateSchemaVersion: z.literal(1),
@@ -126,6 +138,9 @@ export const ProductTemplateManifestSchema = z
126
138
  requiresWorkingDirectory: z.boolean().optional().meta({
127
139
  description: "When true, the operator must choose a working directory at launch (a dev product template they own and keep), rather than getting the auto-assigned per-instance default. The runner's launch UI makes the working-directory field mandatory.",
128
140
  }),
141
+ sideload: ProductTemplateSideloadSchema.optional().meta({
142
+ description: "CUDA sideload bundle needs. Only gpu-inference is declared (gpu-video follows the GPU reservation automatically). The runner mounts the matching bundle when the image is slimmed and the GPU is reserved.",
143
+ }),
129
144
  advanced: ProductTemplateAdvancedSchema.optional().meta({
130
145
  description: "Settings-backed runner knobs the operator may override per instance. Key presence opts the knob into the launch form; `default` pre-fills it (else the runner's daemon-wide default applies).",
131
146
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@norskvideo/ctl-product-template-schema",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -14,6 +14,10 @@
14
14
  "./params": {
15
15
  "types": "./params.d.ts",
16
16
  "default": "./params.js"
17
+ },
18
+ "./read": {
19
+ "types": "./read.d.ts",
20
+ "default": "./read.js"
17
21
  }
18
22
  },
19
23
  "main": "./index.js",
package/read.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { ProductTemplateParameter } from "./index.js";
2
+ import { type ProductTemplateManifest } from "./index.js";
3
+ /**
4
+ * Error taxonomy for product-template lifecycle issues. Codes cover both the
5
+ * "store + extract" path (ctl's ProductTemplateService — extraction, manifest
6
+ * parsing, in-use guarding) and the simpler "store raw bytes" path (mgr —
7
+ * name validation + bytes persistence). Lives with the contract it reports
8
+ * on; @norskvideo/ctl-sdk re-exports it for existing importers.
9
+ */
10
+ export declare class ProductTemplateError extends Error {
11
+ code: "INVALID_NAME" | "NAME_CONFLICT" | "FILE_NOT_FOUND" | "EXTRACTION_FAILED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "COMPOSE_MISSING" | "PARAMETERS_INVALID" | "NOT_FOUND" | "IN_USE" | "PRODUCT_MISMATCH";
12
+ constructor(code: "INVALID_NAME" | "NAME_CONFLICT" | "FILE_NOT_FOUND" | "EXTRACTION_FAILED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "COMPOSE_MISSING" | "PARAMETERS_INVALID" | "NOT_FOUND" | "IN_USE" | "PRODUCT_MISMATCH", message: string);
13
+ }
14
+ /**
15
+ * Where the template's root members come from — the seam between the shared
16
+ * contract and each runner's read mechanics. Two real adapters: ctl reads an
17
+ * extracted directory; mgr streams single members with `tar -xOf`.
18
+ * Returns null for an absent member.
19
+ */
20
+ export interface TarSource {
21
+ readMember(name: string): Promise<string | null> | string | null;
22
+ }
23
+ export interface ProductTemplateContents {
24
+ manifest: ProductTemplateManifest;
25
+ /** `[]` when the template ships no parameters.yaml. */
26
+ parameters: ProductTemplateParameter[];
27
+ /** Whether compose.yml exists at the root. Enforcement is caller policy. */
28
+ hasCompose: boolean;
29
+ }
30
+ /** Validate a product template's members. Throws ProductTemplateError with
31
+ * the canonical taxonomy; forward compatibility (RFC 0001) rides on the
32
+ * schemas' strip-unknown behaviour, pinned in this package's tests. */
33
+ export declare function readProductTemplate(source: TarSource): Promise<ProductTemplateContents>;
package/read.js ADDED
@@ -0,0 +1,67 @@
1
+ // The reading half of the ctl↔product tar contract: which members a product
2
+ // template carries, in what order they are validated, and what error each
3
+ // failure maps to. Both runners re-implemented this (ctl over an extracted
4
+ // dir, mgr over streamed tar members) with byte-identical error strings —
5
+ // the contract now lives beside the schemas it validates, and the read
6
+ // mechanics stay with the callers as TarSource adapters.
7
+ //
8
+ // Policy stays with the callers too: ctl's import refuses a compose-less tar,
9
+ // mgr (which stores raw bytes for relay) does not — so compose presence is a
10
+ // reported fact here, not a verdict.
11
+ import { ParametersFileSchema, ProductTemplateManifestSchema } from "./index.js";
12
+ /**
13
+ * Error taxonomy for product-template lifecycle issues. Codes cover both the
14
+ * "store + extract" path (ctl's ProductTemplateService — extraction, manifest
15
+ * parsing, in-use guarding) and the simpler "store raw bytes" path (mgr —
16
+ * name validation + bytes persistence). Lives with the contract it reports
17
+ * on; @norskvideo/ctl-sdk re-exports it for existing importers.
18
+ */
19
+ export class ProductTemplateError extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.code = code;
24
+ this.name = "ProductTemplateError";
25
+ }
26
+ }
27
+ /** Validate a product template's members. Throws ProductTemplateError with
28
+ * the canonical taxonomy; forward compatibility (RFC 0001) rides on the
29
+ * schemas' strip-unknown behaviour, pinned in this package's tests. */
30
+ export async function readProductTemplate(source) {
31
+ const manifestText = await source.readMember("manifest.json");
32
+ if (manifestText === null) {
33
+ throw new ProductTemplateError("MANIFEST_MISSING", "product template tar must contain manifest.json at the root");
34
+ }
35
+ let manifestRaw;
36
+ try {
37
+ manifestRaw = JSON.parse(manifestText);
38
+ }
39
+ catch (e) {
40
+ throw new ProductTemplateError("MANIFEST_INVALID", `manifest.json is not valid JSON: ${String(e)}`);
41
+ }
42
+ const manifestParsed = ProductTemplateManifestSchema.safeParse(manifestRaw);
43
+ if (!manifestParsed.success) {
44
+ throw new ProductTemplateError("MANIFEST_INVALID", `manifest.json failed schema validation: ${manifestParsed.error.message}`);
45
+ }
46
+ let parameters = [];
47
+ const parametersText = await source.readMember("parameters.yaml");
48
+ if (parametersText !== null) {
49
+ let parametersRaw;
50
+ try {
51
+ parametersRaw = Bun.YAML.parse(parametersText);
52
+ }
53
+ catch (e) {
54
+ throw new ProductTemplateError("PARAMETERS_INVALID", `parameters.yaml is not valid YAML: ${String(e)}`);
55
+ }
56
+ const parametersParsed = ParametersFileSchema.safeParse(parametersRaw);
57
+ if (!parametersParsed.success) {
58
+ throw new ProductTemplateError("PARAMETERS_INVALID", `parameters.yaml failed schema validation: ${parametersParsed.error.message}`);
59
+ }
60
+ parameters = parametersParsed.data.parameters;
61
+ }
62
+ return {
63
+ manifest: manifestParsed.data,
64
+ parameters,
65
+ hasCompose: (await source.readMember("compose.yml")) !== null,
66
+ };
67
+ }