@synopackageland/cli 0.15.0 → 0.15.2

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.2 (2026-10-18)
4
+
5
+ - Preserve command-set process exit codes instead of resetting them to 0 after CLI dispatch. `ctl` failures now reach scripts and CI with their documented exit codes; successful commands still exit 0 and uncaught exceptions exit 1.
6
+ - Add subprocess regression coverage for `ctl` exit codes 0–8, confirmation before writes, and failures from `check`, `build`, `inspect`, `status` and `test`. Document the existing `ctl` exit-code contract and informational discovery/session results.
7
+ - CLI-only patch: templates and SDK fixtures/examples remain pinned to 0.15.1; executor/lifecycle digests are unchanged.
8
+
9
+ 繁中摘要:修正 CLI 在子指令完成後把退出碼覆寫成 0 的問題,讓 `ctl` 失敗正確傳遞至腳本與 CI;成功仍為 0,未捕捉例外仍為 1。新增實際子行程測試,涵蓋 `ctl` 的 0–8 退出碼、寫入確認及其他主要指令的失敗;README 記錄既有退出碼與 discovery/session 資訊結果。此次僅更新 CLI,templates/SDK 維持 0.15.1,executor/lifecycle digest 不變。
10
+
11
+ ## 0.15.1 (2026-10-18)
12
+
13
+ - Require explicit `DSM_E2E_SSH` for e2e tunnels and use `dsm` as the default saved-profile target; set `DSM_E2E_TARGET` to reuse an earlier target name. Anonymize internal NAS fixtures and documentation.
14
+ - Pin templates and SDK fixtures/examples to 0.15.1. Add the 0.15.1 executor/lifecycle digest entry and retain the published 0.15.0 pins unchanged.
15
+ - Cover package-named control.sh refusal logs and rejected `.executor` / `.policy.json` staging cleanup.
16
+ - Executor task definitions change: Apps rebuilt with SDK 0.15.1 require one administrator reapproval after upgrade.
17
+
18
+ 繁中摘要:e2e tunnel 必須明確設定 `DSM_E2E_SSH`,profile 預設 target 改為 `dsm`(沿用舊 profile 時設定 `DSM_E2E_TARGET`);清理內部 NAS 範例資訊。templates/SDK pins 同步 0.15.1;新增同版三架構 executor/lifecycle digest,保留 0.15.0 pins。驗證 control.sh 拒絕 log 的套件名與 runtime copies 清除。**task 定義改變:App 使用新 SDK 重建升級後,管理員需重新核准一次。**
19
+
3
20
  ## 0.15.0 (2026-10-18)
4
21
 
5
22
  - Add `synopkgland ctl` host/session management, authenticated API v1 discovery, explicit command trust, typed invocation and fixed job/pagination/NDJSON/upload flows. Commands keep App backend authorization; writes require confirmation.
@@ -329,7 +346,7 @@ before its timeout.
329
346
  - The window approval of an upgrade (outdated tasks) makes 11 scheduler calls
330
347
  instead of 12: the outdated boot task is deleted by the approval's own first
331
348
  EventScheduler get, made moments before the password, instead of a second
332
- get (2.9 s on vt-nas in t-0484). The control task is still read again by id
349
+ get (2.9 s on the test NAS in t-0484). The control task is still read again by id
333
350
  before its delete (spec/scheduler/v1). A fresh approval stays at 8.
334
351
  繁中:升級核准的排程呼叫由 12 降為 11(boot 任務不再於刪除前重讀)。
335
352
  - A scope approval reads the executor's selection back every second at most
package/README.md CHANGED
@@ -68,6 +68,19 @@ templates checkout and `SYNOPKGLAND_SDK_DIR` to an SDK checkout; SPKs built with
68
68
  packs the local SDK into its frontend (`npm install --install-links`) without building it: run
69
69
  `npm ci && npm run build` in `$SYNOPKGLAND_SDK_DIR/ts` first, and again after SDK changes.
70
70
 
71
+ To check the CLI's DSM terminology rules against templates without a new templates release:
72
+
73
+ ```sh
74
+ SYNOPKGLAND_TEMPLATES_DIR=/path/to/templates npm run test:glossary
75
+ ```
76
+
77
+ This compares `OFF_GLOSSARY` and the Italian sentence rule, including regex sources and
78
+ flags. It supports declaration-only rules with read-only use in `glossaryProblems`;
79
+ post-declaration mutations, aliases and other unsupported rule uses fail the check instead of
80
+ comparing a partial snapshot. The command also fails on drift or missing source. It needs a checkout because templates
81
+ 0.15.1 does not publish `scripts/test/i18n.test.mjs`. The npm gate runs this check when
82
+ `SYNOPKGLAND_TEMPLATES_DIR` is set; otherwise it explicitly reports that the drift check was skipped.
83
+
71
84
  ## Capabilities
72
85
 
73
86
  Add them with `init --with …` or later with `synopkgland add <capability…>`.
@@ -150,12 +163,17 @@ await dismissNotifications(); // when a card shows later
150
163
  ```
151
164
 
152
165
  `runWithTunnel` (the `tunnel --` command) opens an SSH tunnel to the NAS
153
- (`DSM_E2E_SSH`, default `root@vt-nas.syno`) on a free loopback port and runs the
166
+ (`DSM_E2E_SSH`, required, e.g. `admin@nas.example`) on a free loopback port and runs the
154
167
  command with `DSM_E2E_URL` set. It masks DSM's session cookies (`id`, `did`,
155
168
  `_SSID`), `X-SYNO-TOKEN`, `SynoToken` and `_sid` as `***` in the command's
156
169
  output and in the files under `DSM_E2E_OUTPUT_DIR` (default `.e2e`), and makes
157
170
  that directory private (700, files 600); saved sessions keep their cookies.
158
- Accounts come from mode 600 files named by `DSM_E2E_ACCOUNT_<USER>`.
171
+ Set `DSM_E2E_SSH` to your NAS SSH destination in the environment or a local
172
+ untracked `.env`; an unset or blank value fails before SSH or the command starts.
173
+ `nas.example` is a fixture hostname; replace it with your own host. Accounts come
174
+ from mode 600 files named by `DSM_E2E_ACCOUNT_<USER>`. Saved profiles use
175
+ `DSM_E2E_TARGET` (default `dsm`) and the user as their names; set the target to
176
+ an existing profile's target name to keep reusing it.
159
177
  `signIn` and `openDesktop` also register those values as e2e secrets in the
160
178
  test's worker, so e2e masks them in its own report. A test that forwards
161
179
  requests with `context.route` and `route.fetch` should end with
@@ -255,6 +273,37 @@ commands and their schemas. Run the listed alias/command with typed flags,
255
273
  `--help` and `--dry-run`; writes require `--yes`. There is no mutable current
256
274
  context. Use `--host a,b` or `--all-hosts` for bounded parallel operations.
257
275
 
276
+ `ctl` preserves these process exit codes, including with `--json`:
277
+
278
+ | Exit code | Meaning and examples |
279
+ | --- | --- |
280
+ | 0 | Command completed successfully (including help and `--dry-run`). |
281
+ | 1 | Unexpected internal error (`CTL_INTERNAL`), or an uncaught CLI exception. |
282
+ | 2 | Authentication/session failure: `CTL_NOT_AUTHENTICATED`, `CTL_AUTH_FAILED`, `CTL_CREDENTIAL_MISMATCH`, or unavailable OS credential storage. |
283
+ | 3 | Invalid parameters, schema validation or unknown names: `CTL_PARAMS_INVALID`, `CTL_ARGUMENT_INVALID`, `CTL_APP_UNKNOWN`, `CTL_COMMAND_UNKNOWN`. |
284
+ | 4 | Permission refusal that fails the command: `CTL_PERMISSION_DENIED`, `CTL_INVENTORY_DENIED`. |
285
+ | 5 | App/contract failure that fails the command: `CTL_APP_ERROR`, `CTL_RESPONSE_INVALID`, `CTL_JOB_FAILED`. |
286
+ | 6 | Network/HTTP failure or uncertain completion: `CTL_NETWORK`, `CTL_HTTP_ERROR`, `CTL_JOB_TIMEOUT`, `CTL_LOGOUT_FAILED`. |
287
+ | 7 | Confirmation required (`CTL_CONFIRM_REQUIRED`): a write/destructive command or host removal without confirmation; no business operation runs. |
288
+ | 8 | A multi-host command has at least one failed host (including when all hosts fail); inspect each host's error. |
289
+
290
+ For example, `synopkgland ctl studio drafts create --host nas --json` without
291
+ `--yes` returns `ok:false` with `CTL_CONFIRM_REQUIRED` and exits 7 once discovery
292
+ and parameter validation succeed. Missing sessions fail earlier with exit 2.
293
+ Discovery catches per-App description errors and excludes those Apps from its
294
+ results. For example, a changed pinned description (`CTL_DESCRIPTION_CHANGED`)
295
+ or an invalid descriptor (`CTL_DESCRIPTOR_INVALID`) appears in
296
+ `ctl list --host nas --refresh --json` under `data.diagnostics`; the listing still
297
+ returns `ok:true` and exits 0, even if no Apps remain. These diagnostic codes do
298
+ not become process exit codes 4 or 5. Attempting to execute an excluded App with
299
+ `--refresh` instead returns `ok:false`, `CTL_APP_UNKNOWN` and exit 3 because the
300
+ App is absent from discovery results; no business operation runs.
301
+
302
+ `ctl host list/show` can also succeed while reporting `authenticated:false` and
303
+ exit 0. Check discovery `apps`/`diagnostics` and session fields as well as the exit
304
+ code when deciding whether an App is ready.
305
+ After a network or job timeout, query the App's state before retrying a write.
306
+
258
307
  Read `synopkgland skill show ctl` for the AI workflow, batch discovery approval,
259
308
  cache refresh, host removal/revocation and explicit `ctl.commands` authoring.
260
309
  API descriptions are private SPK files named `synopkgland-api.json`, served
package/dist/bin.js CHANGED
@@ -3,9 +3,8 @@ import { loadDotEnv } from "./load-env.js";
3
3
  import { runCli } from "./cli.js";
4
4
  loadDotEnv();
5
5
  // Package deployment is explicit; this CLI does not publish releases.
6
- runCli(process.argv).then(() => 0).then((status) => {
7
- process.exitCode = status;
8
- }).catch((error) => {
6
+ // Successful completion leaves Node's default 0 or the command's exitCode intact.
7
+ runCli(process.argv).catch((error) => {
9
8
  console.error(error instanceof Error ? error.message : String(error));
10
9
  process.exit(1);
11
10
  });
package/dist/cli.d.ts CHANGED
@@ -1 +1,2 @@
1
+ /** Resolves after dispatch; commands may set process.exitCode. Rejections are failures. */
1
2
  export declare function runCli(argv: string[]): Promise<void>;
package/dist/cli.js CHANGED
@@ -275,6 +275,7 @@ async function containerBuild(project, arches, options) {
275
275
  return commitOutputs(options.outDir ? resolve(options.outDir) : project.root, (stage) => forEachArch(arches ?? CONTAINER_ARCH_FAMILIES, jobs, async (arch) => (await build(arch, join(stage, `${packageId}-${arch}.spk`)))
276
276
  .outputPath));
277
277
  }
278
+ /** Resolves after dispatch; commands may set process.exitCode. Rejections are failures. */
278
279
  export async function runCli(argv) {
279
280
  if (argv[2] === "ctl") {
280
281
  const { runCtl } = await import("./ctl/cli.js");
@@ -3,7 +3,7 @@ import { collectInstallParameters } from "./load.js";
3
3
  * Install parameter types that the schema still accepts but that have no working
4
4
  * install path on the live package-only route.
5
5
  *
6
- * Measured on vt-nas 2026-08-27: both end in DSM broken/150 with no Container
6
+ * Measured on the test NAS 2026-08-27: both end in DSM broken/150 with no Container
7
7
  * Manager project, while a no-parameter control package installed fine on the
8
8
  * same NAS. `secret` renders its password field and fails; `homes` renders no
9
9
  * input at all. `check` reports these as warnings so build keeps working
@@ -33,7 +33,7 @@ export function validateContract(parsed, contractFile) {
33
33
  }
34
34
  // DSM 7.4 Package Center refuses an SPK whose package identity exceeds 32
35
35
  // characters -- "Invalid file format", before the install starts and with
36
- // nothing written to synopkg.log. Measured on 10.17.83.200: 32 installs, 33
36
+ // nothing written to synopkg.log. Measured on a DSM 7.4 test NAS: 32 installs, 33
37
37
  // does not.
38
38
  if (typeof contract.package === "string" && contract.package.length > DSM_PACKAGE_IDENTITY_MAX) {
39
39
  const pos = getValuePosition(doc, ["package"]) ?? { line: 1, column: 1 };
@@ -164,7 +164,7 @@ function validateInstallParameters(parsed, contractFile) {
164
164
  }
165
165
  }
166
166
  // A45/A46: both types are in the schema enum but neither has a working
167
- // install path on the live package-only route -- measured on vt-nas as DSM
167
+ // install path on the live package-only route -- measured on the test NAS as DSM
168
168
  // broken/150 with no Container Manager project, against a no-parameter
169
169
  // control that installed fine. Refusing here is what makes the failure
170
170
  // honest: `homes` used to validate, build a valid SPK, and only break at
package/dist/e2e/dsm.d.ts CHANGED
@@ -17,7 +17,7 @@ type Page = ReturnType<NonNullable<ReturnType<typeof surfaceOf>>["page"]>;
17
17
  type Locator = ReturnType<Page["locator"]>;
18
18
  /** The DSM URL the browser opens: DSM_E2E_URL, else the local end of the SSH tunnel. */
19
19
  export declare function dsmUrl(env?: Env): string;
20
- /** ssh arguments forwarding the loopback ports of `forwards` to the NAS. */
20
+ /** ssh arguments forwarding loopback ports to the NAS; DSM_E2E_SSH is required. */
21
21
  export declare function tunnelArgs(env?: Env): string[];
22
22
  /** A loopback port nothing listens on now, for a tunnel that names none. */
23
23
  export declare function freePort(): Promise<number>;
@@ -122,7 +122,7 @@ export declare function waitForDesktop(page?: Page): Promise<void>;
122
122
  /**
123
123
  * Closes every DSM notification card; returns how many were closed. DSM shows
124
124
  * them a moment after the desktop (the admin's "Admin account security" card
125
- * about 2 s later on vt-nas), so it first waits up to
125
+ * about 2 s later on a DSM 7.4 test NAS), so it first waits up to
126
126
  * DSM_E2E_NOTIFICATION_WAIT_MS (default 5000) for one.
127
127
  */
128
128
  export declare function dismissNotifications(page?: Page, env?: Env): Promise<number>;
package/dist/e2e/dsm.js CHANGED
@@ -41,11 +41,14 @@ function forwards(env) {
41
41
  ...extra(env.DSM_E2E_TUNNEL_HTTPS_PORT, env.DSM_E2E_REMOTE_HTTPS_PORT, "443"),
42
42
  ];
43
43
  }
44
- /** ssh arguments forwarding the loopback ports of `forwards` to the NAS. */
44
+ /** ssh arguments forwarding loopback ports to the NAS; DSM_E2E_SSH is required. */
45
45
  export function tunnelArgs(env = process.env) {
46
+ const target = env.DSM_E2E_SSH?.trim();
47
+ if (!target)
48
+ throw new Error("DSM_E2E_SSH is required to open a NAS tunnel; set it to user@nas.example (using your NAS host).");
46
49
  return ["-N", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes",
47
50
  ...forwards(env).flatMap(([local, remote]) => ["-L", `127.0.0.1:${local}:127.0.0.1:${remote}`]),
48
- env.DSM_E2E_SSH || "root@vt-nas.syno"];
51
+ target];
49
52
  }
50
53
  /** A loopback port nothing listens on now, for a tunnel that names none. */
51
54
  export function freePort() {
@@ -80,12 +83,13 @@ export async function runWithTunnel(command, outer = process.env) {
80
83
  throw new Error("runWithTunnel needs a command to run.");
81
84
  const port = Number(outer.DSM_E2E_TUNNEL_PORT || await freePort());
82
85
  const env = { ...outer, DSM_E2E_TUNNEL_PORT: String(port) };
86
+ const args = tunnelArgs(env);
83
87
  const ports = forwards(env).map(([local]) => Number(local));
84
88
  for (const used of ports) {
85
89
  if (await portOpen(used))
86
90
  throw new Error(`Port ${used} is already in use; name another DSM_E2E_TUNNEL_* port or close it before opening the tunnel.`);
87
91
  }
88
- const ssh = spawn(env.DSM_E2E_SSH_BIN || "ssh", tunnelArgs(env), { stdio: ["ignore", "ignore", "inherit"] });
92
+ const ssh = spawn(env.DSM_E2E_SSH_BIN || "ssh", args, { stdio: ["ignore", "ignore", "inherit"] });
89
93
  let sshExited = false;
90
94
  ssh.once("exit", () => { sshExited = true; });
91
95
  try {
@@ -93,7 +97,7 @@ export async function runWithTunnel(command, outer = process.env) {
93
97
  for (const waiting of ports) {
94
98
  while (!(await portOpen(waiting))) {
95
99
  if (sshExited || Date.now() > deadline)
96
- throw new Error(`The SSH tunnel to ${tunnelArgs(env).at(-1)} did not open on port ${waiting}.`);
100
+ throw new Error(`The SSH tunnel to ${args.at(-1)} did not open on port ${waiting}.`);
97
101
  await new Promise(accept => setTimeout(accept, 100));
98
102
  }
99
103
  }
@@ -364,7 +368,7 @@ const notificationCloseSelector = ".v-notification-container .v-notification-pan
364
368
  /**
365
369
  * Closes every DSM notification card; returns how many were closed. DSM shows
366
370
  * them a moment after the desktop (the admin's "Admin account security" card
367
- * about 2 s later on vt-nas), so it first waits up to
371
+ * about 2 s later on a DSM 7.4 test NAS), so it first waits up to
368
372
  * DSM_E2E_NOTIFICATION_WAIT_MS (default 5000) for one.
369
373
  */
370
374
  export async function dismissNotifications(page = dsmPage(), env = process.env) {
@@ -454,7 +458,7 @@ async function restoredSessionLive(page) {
454
458
  */
455
459
  export async function signIn(app, user = "admin", env = process.env) {
456
460
  const url = dsmUrl(env);
457
- const file = profilePath(env.DSM_E2E_TARGET || "vt-nas", user, env);
461
+ const file = profilePath(env.DSM_E2E_TARGET || "dsm", user, env);
458
462
  const context = surface().context();
459
463
  const profile = loadProfile(file, url);
460
464
  if (profile)
@@ -8,7 +8,7 @@ import type { SharePermissionRow } from "./share-permission.js";
8
8
  * the install proceed, while an empty list would read as a real denial and
9
9
  * refuse the install on a transport error.
10
10
  *
11
- * The APIs used were confirmed present on DSM 7.4 (vt-nas):
11
+ * The APIs used were confirmed present on DSM 7.4 (the test NAS):
12
12
  * `SYNO.Core.Share.Permission`, `SYNO.Core.Group`, `SYNO.Core.Group.Member`.
13
13
  * `SYNO.Core.User.Group` also exists on the NAS but has no generated
14
14
  * synoagentcli operation, which is why membership is derived group-by-group.
@@ -10,7 +10,7 @@
10
10
  * extra_values directly, and share permission can change after install.
11
11
  *
12
12
  * The rows are SYNO.Core.Share.Permission v1 output verbatim. Read the tests
13
- * before changing the predicate: on vt-nas, `newadmin` has is_writable=false on
13
+ * before changing the predicate: on the test NAS, `newadmin` has is_writable=false on
14
14
  * its own row for `pkgland-p0b` and still writes there, because the
15
15
  * `administrators` group row carries the grant. A user-row-only check rejects
16
16
  * the only configuration with live proof behind it.
@@ -10,7 +10,7 @@
10
10
  * extra_values directly, and share permission can change after install.
11
11
  *
12
12
  * The rows are SYNO.Core.Share.Permission v1 output verbatim. Read the tests
13
- * before changing the predicate: on vt-nas, `newadmin` has is_writable=false on
13
+ * before changing the predicate: on the test NAS, `newadmin` has is_writable=false on
14
14
  * its own row for `pkgland-p0b` and still writes there, because the
15
15
  * `administrators` group row carries the grant. A user-row-only check rejects
16
16
  * the only configuration with live proof behind it.
@@ -9,5 +9,16 @@
9
9
  "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
10
10
  },
11
11
  "goToolchain": "go1.26.9"
12
+ },
13
+ "0.15.1": {
14
+ "x86_64": "4f47fb43f6e60dbad064afb0bbec96dd009fa046670e2c4b590b128a855cb404",
15
+ "aarch64": "c903b09ad657a838f0c62b6688e65e9094572b22b969770fca92fcc3640a5364",
16
+ "armv7": "8178d90de7ea720530bb9496e50ed118086b0e57382d84675aadc68f482d219f",
17
+ "lifecycle": {
18
+ "x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
19
+ "aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
20
+ "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
21
+ },
22
+ "goToolchain": "go1.26.9"
12
23
  }
13
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "scripts": {
20
20
  "build": "rm -rf dist && tsc",
21
21
  "test": "npm run build && node --import tsx --test test/*.test.ts test/ctl/*.test.ts",
22
+ "test:glossary": "node --test scripts/test/glossary-drift.test.mjs",
22
23
  "test:scripts": "node --test scripts/test/gate.test.mjs scripts/test/release.test.mjs scripts/test/pack-docker.test.mjs scripts/test/npm-pack.test.mjs scripts/test/executor-bump.test.mjs scripts/test/smoke-exports.test.mjs scripts/test/executor-digests.test.mjs scripts/test/parallel.test.mjs scripts/test/scaffold-matrix.test.mjs scripts/test/frontend-deps.test.mjs scripts/test/docker-gate.test.mjs scripts/test/gate-plan.test.mjs scripts/test/gates.test.mjs",
23
24
  "gates": "bash scripts/ci/gates.sh",
24
25
  "gates:fast": "bash scripts/ci/gates.sh --fast",
@@ -26,7 +27,7 @@
26
27
  },
27
28
  "dependencies": {
28
29
  "@napi-rs/keyring": "^2.1.1",
29
- "@synopackageland/templates": "0.15.0",
30
+ "@synopackageland/templates": "0.15.1",
30
31
  "ajv": "^8.17.1",
31
32
  "commander": "^13.1.0",
32
33
  "json-canonicalize": "^3.0.1",
package/skills/SKILL.md CHANGED
@@ -129,7 +129,7 @@ sweep forward the cancel instead of admitting it.
129
129
  `ExportExample` creates a private JSON file, calls `MoveFromStaging` once, and
130
130
  repeats the original request on repeated IDs. Review `example.go` to
131
131
  adapt a real producer; show Saved only from a protected committed receipt.
132
- - Wizard support is measured on vt-nas DSM 7.4 via a session-only SSH local
132
+ - Wizard support is measured on the test NAS DSM 7.4 via a session-only SSH local
133
133
  forward. This is not proof of other DSM releases or full writer/reboot behavior.
134
134
 
135
135
  ## 開啟方式
@@ -23,7 +23,7 @@ compose:
23
23
  network: {egress: isolated}
24
24
  # Private package binds are rejected by DSM build; keep compatible upstream named volumes.
25
25
  identity_gateway:
26
- sdk: 0.15.0
26
+ sdk: 0.15.1
27
27
  mode: none
28
28
  access: dsm-users
29
29
  listen: {http: 28000}
@@ -151,7 +151,7 @@ declared availability readiness, LAN isolation/egress, protected grant lifecycle
151
151
  ACLs, two-user visibility and clean install/upgrade/reboot/stop/uninstall before
152
152
  calling the package supported on a NAS. No NAS is accessed by the build gate.
153
153
 
154
- The unified Compose contract requires SDK, CLI and templates 0.15.0. Use the published, matching versions together; the legacy packaging route and bridge publish mode are removed.
154
+ The unified Compose contract requires SDK, CLI and templates 0.15.1. Use the published, matching versions together; the legacy packaging route and bridge publish mode are removed.
155
155
 
156
156
  Primary override files, and each included override set, must share a directory. Cross-directory override bases are rejected before loader I/O. Included implicit .env files are confined, inventoried and preserved; symlinks are rejected.
157
157
 
@@ -198,7 +198,7 @@ resources and does not migrate or delete retained data.
198
198
  Bridge web ingress uses a dedicated `sdk_ingress` internal bridge. The web service
199
199
  receives a fixed IPv4 address, and the package-account gateway connects directly
200
200
  to its declared container port. Internal bridges were reachable by package-account
201
- hooks in the sa3200d and vt-nas feasibility probes. No HAProxy helper, published
201
+ hooks in feasibility probes on an SA3200D and another test NAS. No HAProxy helper, published
202
202
  container port, Docker socket or host root is required. Explicit internet egress
203
203
  still adds the existing outbound bridge; other App services retain their internal networks.
204
204
 
@@ -231,7 +231,7 @@ Legacy `.sc` residue is not purged by this topology. Migrating an already publis
231
231
  installation requires a separately authorized cleanup or an official worker fix;
232
232
  a no-publish rebuild cannot claim to remove historical service registrations.
233
233
 
234
- On vt-nas DSM 7.4/Docker 24.0.2, internal networks blocked tested external IPv4
234
+ On the test NAS DSM 7.4/Docker 24.0.2, internal networks blocked tested external IPv4
235
235
  and external DNS, but allowed NAS host services through the bridge gateway and
236
236
  NAS LAN address. Therefore `isolated` is not a host-service firewall. IPv6 had
237
237
  no usable default route on this fixture, which is not proof of an IPv6 firewall.
@@ -245,7 +245,7 @@ Set-Cookie. Other App sessions/native cookies retain their normal semantics.
245
245
  Cookies are shared across ports on the same hostname, so filtering only this
246
246
  package's SDK cookie would leak another App's SDK session to an upstream.
247
247
 
248
- Historical publish-topology vt-nas validation was release-blocked: the Container Manager
248
+ Historical publish-topology validation on the test NAS was release-blocked: the Container Manager
249
249
  24.0.2-1706 docker-project worker can return profile error 1202 on update,
250
250
  which Package Center reports as postinst-worker failure 277. The owned test
251
251
  project and a real Vaultwarden package reproduced this; switching Compose
@@ -328,7 +328,7 @@ The [docker-project contract](https://help.synology.com/developer-guide/resource
328
328
  documents project deletion on PREUNINST and build flags, without a separate
329
329
  package-account profile-release option.
330
330
 
331
- The original isolated Header fixture on vt-nas installed and started from a
331
+ The original isolated Header fixture on the test NAS installed and started from a
332
332
  clean configuration, then its upgrade still returned port conflict → 1202 →
333
333
  277. Historical `.sc` residue alone is therefore insufficient to explain or
334
334
  resolve that environment's upgrade failure. Preserve the timestamp/PID chain;
@@ -40,8 +40,8 @@ rootOps 與 delegate 已共用管理員 PasswordConfirm 核准的 protected exec
40
40
  backend 寫 `runtime: python|node|rust`(`runtime: go` 等同 `kind: go`)。CGI 是 SDK 的 Go runtime(`bridge/cmd/runtime`),它驗證 DSM session、entry 與 `permissions.json` 後,以套件帳號(不是 root)每個請求啟動一次 App。App 放在 ui 以外的目錄,打包成 `target/app/`,build 寫入 `bridge.json`(spec/bridge/v1)並把它與 `permissions.json` 的 sha256 編進 runtime。
41
41
 
42
42
  ```yaml
43
- backend: {runtime: python, dir: app, entry: main.py, sdk: 0.15.0}
44
- backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.15.0, node: {package: Node.js_v20}}
43
+ backend: {runtime: python, dir: app, entry: main.py, sdk: 0.15.1}
44
+ backend: {runtime: node, dir: app, entry: main.mjs, sdk: 0.15.1, node: {package: Node.js_v20}}
45
45
  backend: {runtime: rust, dir: app, entry: hello, methods: [hello.greet]}
46
46
  ```
47
47
 
@@ -213,7 +213,7 @@ sweep forward the cancel instead of admitting it.
213
213
  `ExportExample` creates a private JSON file, calls `MoveFromStaging` once, and
214
214
  repeats the original request on repeated IDs. Review `example.go` to
215
215
  adapt a real producer; show Saved only from a protected committed receipt.
216
- - Wizard support is measured on vt-nas DSM 7.4 via a session-only SSH local
216
+ - Wizard support is measured on the test NAS DSM 7.4 via a session-only SSH local
217
217
  forward. This is not proof of other DSM releases or full writer/reboot behavior.
218
218
 
219
219
  ## 開啟方式
@@ -118,7 +118,7 @@ sweep forward the cancel instead of admitting it.
118
118
  `ExportExample` creates a private JSON file, calls `MoveFromStaging` once, and
119
119
  repeats the original request on repeated IDs. Review `example.go` to
120
120
  adapt a real producer; show Saved only from a protected committed receipt.
121
- - Wizard support is measured on vt-nas DSM 7.4 via a session-only SSH local
121
+ - Wizard support is measured on the test NAS DSM 7.4 via a session-only SSH local
122
122
  forward. This is not proof of other DSM releases or full writer/reboot behavior.
123
123
 
124
124
  The pinned scope-selection task also binds the protected generation observed before confirmation. The executor compares it under the publish lock and refuses stale tasks, so a retained task cannot replay an older selection after another confirmation.