@synopackageland/cli 0.20.0 → 0.21.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.1 (2026-10-21)
4
+
5
+ - Resolve SDK/App 0.21.1 to the verified immutable executor/v0.20.0 release while keeping exact SDK-version digest checks.
6
+ - Pin templates 0.21.1 with SDK/App 0.21.1 and UI 0.13.1 for new projects.
7
+
8
+ ## 0.21.0 (2026-10-21)
9
+
10
+ - Validate and preserve `compose.devices.gpu` in check, build and inspect for optional Intel render-node access and software fallback.
11
+ - Pin SDK/templates 0.21.0 and reuse immutable executor/v0.20.0 with verified unchanged executor/lifecycle digests.
12
+
3
13
  ## 0.20.0 (2026-10-21)
4
14
 
5
15
  - Align CLI/templates and the supported SDK minor line at 0.20; retain published UI 0.12.5.
package/README.md CHANGED
@@ -367,3 +367,20 @@ unencrypted-HTTP limitations before opening it. Native resource/startup failures
367
367
  fail closed; they do not silently expose a direct listener. The native runtime
368
368
  requires the corresponding SDK release; this CLI change does not publish that
369
369
  release or change existing manifest version pins.
370
+
371
+ Compose Apps may opt in to Intel render devices with
372
+ `compose.devices.gpu.services: [media]`. The matching SDK discovers exact devices
373
+ and the native `videodriver` GID at installation; the CLI emits DSM's
374
+ `video-driver` resource. `check` rejects arbitrary paths/GIDs and elevated GPU
375
+ services. `inspect --json` includes `compose.devices.gpu` with the runtime path
376
+ policy, GID source and software fallback, plus outstanding DSM acceptance
377
+ blockers. Keep the package UID and let the App validate its upstream encoder;
378
+ absence of a GPU produces no device mapping. See the matching SDK's Compose GPU
379
+ documentation for the security boundary and hardware validation limits.
380
+
381
+ DSM's native provider adds NET_BIND_SERVICE to the bounding set for Compose
382
+ packages. The SDK still emits `cap_drop: [ALL]` and requests no `cap_add`; the
383
+ nonroot package process with no-new-privileges has zero effective, permitted and
384
+ ambient capabilities on the tested provider. This general platform behavior is
385
+ not a GPU blocker. Plan metadata describes SDK policy, not an attestation of the
386
+ provider's final container configuration; see the SDK Compose security boundary.
package/dist/cli.js CHANGED
@@ -344,6 +344,7 @@ export async function runCli(argv) {
344
344
  ...("compose" in loaded.manifest && loaded.manifest.compose
345
345
  ? {
346
346
  compose: {
347
+ ...(loaded.manifest.compose.plan.devices ? { devices: loaded.manifest.compose.plan.devices } : {}),
347
348
  images: loaded.manifest.compose.plan.images,
348
349
  services: loaded.manifest.compose.plan.services,
349
350
  mountsBlocked: loaded.manifest.compose.plan.mounts_blocked,
@@ -688,15 +689,18 @@ export async function runCli(argv) {
688
689
  outDir: undefined,
689
690
  }),
690
691
  });
692
+ const builtManifest = standalone && !isNativeLayout(standalone) ? loadStandaloneManifest(standalone) : undefined;
693
+ const devices = builtManifest && builtManifest.ok ? builtManifest.manifest.compose?.plan.devices : undefined;
691
694
  const approval = await Promise.all(outputs.map(async (output) => ({ output, ...await spkApprovalDiff(output, options.baseline && resolve(options.baseline)) })));
692
695
  const lines = [
693
696
  ...approval.map(item => item.output + "\n" + formatApprovalDiff(item)),
694
697
  ...outputs.map((output) => `Built ${output}`),
698
+ ...(devices?.gpu ? [`GPU: ${devices.gpu.paths}; GID source: ${devices.gpu.gid_source}; fallback: ${devices.gpu.fallback}`] : []),
695
699
  ...(reproducible ? reproducibleLines(reproducible) : []),
696
700
  ];
697
701
  if (reproducible && !reproducible.identical)
698
702
  throw Object.assign(failure("BUILD_NOT_REPRODUCIBLE", lines.join("\n"), "Find the payload files that differ between builds (timestamps, random paths, unpinned dependencies) and retry after fixing them."), { report: { outputs, reproducible } });
699
- emit("build", { outputs, approval, ...(reproducible ? { reproducible } : {}) }, lines.join("\n"));
703
+ emit("build", { outputs, approval, ...(devices ? { compose: { devices } } : {}), ...(reproducible ? { reproducible } : {}) }, lines.join("\n"));
700
704
  });
701
705
  if (json)
702
706
  console.log = (...args) => console.error(...args);
@@ -1 +1,24 @@
1
- { "0.20.0": { "x86_64": "e27cfeb85fed221b06f9c58166f02c0f701a724308cd17c96a43a843a0b5c4aa", "aarch64": "908db00cbd37d072f6ffc339e7bca24da5efb8cddd4befcbad02c7398beb7191", "armv7": "a0f35d62ec80a1bf3fb49f5e2d1a5f7576eda569a6fd0695084cec6cb8e100bc", "lifecycle": { "x86_64": "9bb53301670b43cdcd6e21cebac99ab7abd82962d874f32423a81e6cb49d4972", "aarch64": "38fe873c86eb102c850baa1f304114a83a31d1df72defd22792bc37610fec9bb", "armv7": "256fb348b275da7ac5728a3cb7cb7c65660de0d5c2c59430ab4f5ade1b0a3681" }, "goToolchain": "go1.26.9" } }
1
+ {
2
+ "0.21.0": {
3
+ "x86_64": "e27cfeb85fed221b06f9c58166f02c0f701a724308cd17c96a43a843a0b5c4aa",
4
+ "aarch64": "908db00cbd37d072f6ffc339e7bca24da5efb8cddd4befcbad02c7398beb7191",
5
+ "armv7": "a0f35d62ec80a1bf3fb49f5e2d1a5f7576eda569a6fd0695084cec6cb8e100bc",
6
+ "lifecycle": {
7
+ "x86_64": "9bb53301670b43cdcd6e21cebac99ab7abd82962d874f32423a81e6cb49d4972",
8
+ "aarch64": "38fe873c86eb102c850baa1f304114a83a31d1df72defd22792bc37610fec9bb",
9
+ "armv7": "256fb348b275da7ac5728a3cb7cb7c65660de0d5c2c59430ab4f5ade1b0a3681"
10
+ },
11
+ "goToolchain": "go1.26.9"
12
+ },
13
+ "0.21.1": {
14
+ "x86_64": "e27cfeb85fed221b06f9c58166f02c0f701a724308cd17c96a43a843a0b5c4aa",
15
+ "aarch64": "908db00cbd37d072f6ffc339e7bca24da5efb8cddd4befcbad02c7398beb7191",
16
+ "armv7": "a0f35d62ec80a1bf3fb49f5e2d1a5f7576eda569a6fd0695084cec6cb8e100bc",
17
+ "lifecycle": {
18
+ "x86_64": "9bb53301670b43cdcd6e21cebac99ab7abd82962d874f32423a81e6cb49d4972",
19
+ "aarch64": "38fe873c86eb102c850baa1f304114a83a31d1df72defd22792bc37610fec9bb",
20
+ "armv7": "256fb348b275da7ac5728a3cb7cb7c65660de0d5c2c59430ab4f5ade1b0a3681"
21
+ },
22
+ "goToolchain": "go1.26.9"
23
+ }
24
+ }
@@ -36,8 +36,8 @@ export declare function sdkRequirement(backendDir: string): string | undefined;
36
36
  export declare function goModulePath(backendDir: string): string | undefined;
37
37
  /**
38
38
  * The module directories of one SDK version. SYNOPKGLAND_SDK_DIR names a
39
- * local sdk checkout (development and gates only); otherwise both modules
40
- * come from `go mod download` of exactly that version.
39
+ * local sdk checkout (development and gates only); otherwise modules come from
40
+ * `go mod download`; unchanged executor releases are pinned above.
41
41
  */
42
42
  export declare function sdkSource(version: string, env?: NodeJS.ProcessEnv, download?: (spec: string) => string): SdkSource;
43
43
  /** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
@@ -75,16 +75,18 @@ function goMod(backendDir) {
75
75
  return '';
76
76
  }
77
77
  }
78
+ /** SDK releases that reuse an immutable executor release with identical payload bytes. */
79
+ const EXECUTOR_RELEASES = { '0.21.0': '0.20.0', '0.21.1': '0.20.0' };
78
80
  /**
79
81
  * The module directories of one SDK version. SYNOPKGLAND_SDK_DIR names a
80
- * local sdk checkout (development and gates only); otherwise both modules
81
- * come from `go mod download` of exactly that version.
82
+ * local sdk checkout (development and gates only); otherwise modules come from
83
+ * `go mod download`; unchanged executor releases are pinned above.
82
84
  */
83
85
  export function sdkSource(version, env = process.env, download = (spec) => goModDownload(spec, env)) {
84
86
  const dir = env.SYNOPKGLAND_SDK_DIR;
85
87
  if (dir)
86
88
  return { version, goDir: dir, executorDir: join(dir, 'executor'), local: true };
87
- return { version, goDir: download(`${SDK_MODULE}@v${version}`), executorDir: download(`${EXECUTOR_MODULE}@v${version}`), local: false };
89
+ return { version, goDir: download(`${SDK_MODULE}@v${version}`), executorDir: download(`${EXECUTOR_MODULE}@v${EXECUTOR_RELEASES[version] ?? version}`), local: false };
88
90
  }
89
91
  const downloaded = new Map();
90
92
  /** The module cache directory of module@version; the cache is read-only, so builds copy it first. */
@@ -1,5 +1,10 @@
1
1
  /** SDK planner errors accepted at this versioned tool boundary. */
2
2
  export const COMPOSE_ERRORS = {
3
+ COMPOSE_GPU_INVALID: {
4
+ code: "COMPOSE_GPU_INVALID",
5
+ description: "The GPU declaration requires bounded package-UID services.",
6
+ suggestion: "Declare compose.devices.gpu.services with distinct active service names. Keep package UID, no added capabilities, and let the SDK discover Intel render devices and the native videodriver GID.",
7
+ },
3
8
  COMPOSE_STARTUP_INVALID: {
4
9
  code: "COMPOSE_STARTUP_INVALID",
5
10
  description: "The container startup budget is invalid.",
@@ -4,6 +4,15 @@ import { type Daemon } from "./daemon.js";
4
4
  import type { SharedFolder } from "./shared-folder.js";
5
5
  import type { StandaloneError, StandaloneManifest } from "./manifest.js";
6
6
  export interface ComposePlan {
7
+ devices?: {
8
+ gpu?: {
9
+ services: string[];
10
+ resource: string;
11
+ gid_source: string;
12
+ paths: string;
13
+ fallback: string;
14
+ };
15
+ };
7
16
  app_data?: ComposeAppData;
8
17
  startup_timeout_seconds?: number;
9
18
  ingress?: {
@@ -72,7 +81,7 @@ export interface ComposeApp {
72
81
  document: Record<string, any>;
73
82
  plan: ComposePlan;
74
83
  }
75
- export declare const COMPOSE_FIELDS: readonly ["startup_timeout_seconds", "resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
84
+ export declare const COMPOSE_FIELDS: readonly ["devices", "startup_timeout_seconds", "resource_prefix", "files", "env_files", "profiles", "web", "data", "shares", "network", "services", "helpers", "sdk"];
76
85
  export declare function readComposeApp(value: unknown, root: string, arches: NativeArchFamily[], fail: (e: StandaloneError) => void, context?: {
77
86
  package?: string;
78
87
  sdk?: string;
@@ -12,6 +12,7 @@ import { goChildEnv } from "../runtime/go-env.js";
12
12
  import { sdkSource } from "../runtime/sdk-source.js";
13
13
  import { readDaemon } from "./daemon.js";
14
14
  export const COMPOSE_FIELDS = [
15
+ "devices",
15
16
  "startup_timeout_seconds",
16
17
  "resource_prefix",
17
18
  "files",
@@ -68,7 +69,7 @@ export function readComposeApp(value, root, arches, fail, context = {}) {
68
69
  if (!record(value) ||
69
70
  Object.keys(value).some((k) => !COMPOSE_FIELDS.includes(k)) ||
70
71
  !Array.isArray(value.files))
71
- return invalid("compose requires files, with optional env_files, profiles, web, data, shares, network, services, helpers and sdk; old file/targets syntax is removed.");
72
+ return invalid("compose requires files, with optional env_files, profiles, web, data, shares, devices, network, services, helpers and sdk; old file/targets syntax is removed.");
72
73
  if (value.sdk !== undefined &&
73
74
  (typeof value.sdk !== "string" || !/^\d+\.\d+\.\d+$/.test(value.sdk)))
74
75
  return invalid("compose.sdk must name the matching SDK release.", "SDK_COMPOSE_UNSUPPORTED");
@@ -199,6 +200,7 @@ export function writeComposePayload(c, root, target, gateway) {
199
200
  mkdirSync(target, { recursive: true });
200
201
  for (const [name, content] of Object.entries({
201
202
  "docker-compose.yml": c.plan.effective,
203
+ ...(c.plan.devices?.gpu ? { "gpu.template.yaml": c.plan.effective } : {}),
202
204
  "generated.override.yaml": c.plan.override,
203
205
  "compose.lock.json": JSON.stringify(c.plan.lock, null, 2) + "\n",
204
206
  "request.json": JSON.stringify({ declaration: c.declaration, sdk: c.sdk, gateway }, null, 2) + "\n",
@@ -25,6 +25,7 @@ export interface SpkInspection {
25
25
  requiresAdminApproval: boolean;
26
26
  }>;
27
27
  compose?: {
28
+ devices?: ComposeApp["plan"]["devices"];
28
29
  ingress?: ComposeApp["plan"]["ingress"];
29
30
  images: unknown[];
30
31
  services: unknown[];
@@ -608,6 +608,8 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
608
608
  if (composeSource(c) !==
609
609
  readFileSync(join(root, "compose/docker-compose.yml"), "utf8"))
610
610
  throw new Error("worker Compose differs from the SDK plan");
611
+ if (c.plan.devices?.gpu && readFileSync(join(root, "compose/gpu.template.yaml"), "utf8") !== c.plan.effective)
612
+ throw new Error("GPU template differs from the SDK plan");
611
613
  if (c.plan.override !==
612
614
  readFileSync(join(root, "compose/generated.override.yaml"), "utf8"))
613
615
  throw new Error("generated override differs from the SDK plan");
@@ -732,6 +734,7 @@ async function inspectUnifiedCompose(dir, payload, metadata, findings) {
732
734
  if (storageProblem)
733
735
  throw new Error(`[${storageProblem.code}] ${storageProblem.message}`);
734
736
  base.compose = {
737
+ ...(c.plan.devices ? { devices: c.plan.devices } : {}),
735
738
  ...(c.plan.ingress ? { ingress: c.plan.ingress } : {}),
736
739
  images: c.plan.images,
737
740
  services: c.plan.services,
@@ -1041,7 +1044,7 @@ function checkConf(dir, findings, kind, background, sharedFolder, pkg, portal, c
1041
1044
  if (!resource["docker-project"])
1042
1045
  findings.push("the container is missing the docker-project resource");
1043
1046
  for (const key of Object.keys(resource)) {
1044
- if (!["docker-project", "webservice"].includes(key))
1047
+ if (!["docker-project", "webservice", ...(compose?.plan.devices?.gpu ? ["video-driver"] : [])].includes(key))
1045
1048
  findings.push(`Unknown container resource: ${key}`);
1046
1049
  }
1047
1050
  }
@@ -11,6 +11,7 @@ export function renderStandaloneResource(m, portal) {
11
11
  const dataShare = m.sharedFolder && sharedFolderResource(m.package, m.sharedFolder);
12
12
  const webConfig = { ...(portal ? portalResource()['web-config'] : {}), ...nativeGatewayResource(m) };
13
13
  const resource = {
14
+ ...(m.compose?.plan.devices?.gpu && !m.compose.plan.mounts_blocked ? { "video-driver": {} } : {}),
14
15
  ...(m.compose && !m.compose.plan.mounts_blocked
15
16
  ? renderDockerProjectResource(m.package, "compose", false, m.compose.plan.project_name)
16
17
  : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.20.0",
3
+ "version": "0.21.1",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@napi-rs/keyring": "^2.1.1",
30
- "@synopackageland/templates": "0.20.0",
30
+ "@synopackageland/templates": "0.21.1",
31
31
  "ajv": "^8.17.1",
32
32
  "commander": "^13.1.0",
33
33
  "json-canonicalize": "^3.0.1",