@synopackageland/cli 0.10.2 → 0.10.4

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +259 -1696
  2. package/README.md +21 -2
  3. package/assets/login-style/mask.png +0 -0
  4. package/assets/login-style/wallpaper-thumbnail.jpg +0 -0
  5. package/assets/login-style/wallpaper.jpg +0 -0
  6. package/dist/build-spk/generator.js +10 -1
  7. package/dist/build-spk/login-style.d.ts +55 -0
  8. package/dist/build-spk/login-style.js +115 -0
  9. package/dist/cli.js +22 -1
  10. package/dist/error-registry.js +37 -1
  11. package/dist/host/dsm-client.js +6 -9
  12. package/dist/init.js +9 -5
  13. package/dist/runtime/delegate-contract.d.ts +2 -92
  14. package/dist/runtime/delegate-contract.js +2 -24
  15. package/dist/runtime/executor-digests.json +22 -0
  16. package/dist/runtime/executor-policy-check.d.ts +1 -1
  17. package/dist/runtime/executor-policy-check.js +5 -7
  18. package/dist/runtime/executor-policy.d.ts +2 -0
  19. package/dist/runtime/executor-policy.js +5 -1
  20. package/dist/runtime/root-programs.d.ts +2 -0
  21. package/dist/runtime/sdk-source.d.ts +4 -0
  22. package/dist/runtime/sdk-source.js +7 -0
  23. package/dist/runtime/sdk-timeouts.d.ts +23 -0
  24. package/dist/runtime/sdk-timeouts.js +44 -0
  25. package/dist/skill.js +1 -0
  26. package/dist/standalone/app-scripts.d.ts +26 -0
  27. package/dist/standalone/app-scripts.js +141 -0
  28. package/dist/standalone/bridge-app.d.ts +2 -0
  29. package/dist/standalone/bridge-app.js +9 -1
  30. package/dist/standalone/bridge-backend.d.ts +1 -1
  31. package/dist/standalone/build.js +5 -2
  32. package/dist/standalone/dsm-window.d.ts +14 -3
  33. package/dist/standalone/dsm-window.js +58 -12
  34. package/dist/standalone/host-adapter.d.ts +1 -1
  35. package/dist/standalone/host-adapter.js +1 -1
  36. package/dist/standalone/host-file-navigation.d.ts +6 -2
  37. package/dist/standalone/host-file-navigation.js +8 -2
  38. package/dist/standalone/inspect-spk.d.ts +9 -0
  39. package/dist/standalone/inspect-spk.js +60 -6
  40. package/dist/standalone/lifecycle.js +3 -1
  41. package/dist/standalone/manifest.d.ts +6 -0
  42. package/dist/standalone/manifest.js +51 -3
  43. package/dist/standalone/portal-window.d.ts +6 -6
  44. package/dist/standalone/portal-window.js +12 -32
  45. package/dist/standalone/request-timeout.d.ts +6 -3
  46. package/dist/standalone/request-timeout.js +10 -4
  47. package/dist/standalone/root-ops.d.ts +3 -2
  48. package/dist/standalone/root-ops.js +9 -3
  49. package/dist/standalone/root-window.d.ts +2 -2
  50. package/dist/standalone/root-window.js +191 -53
  51. package/dist/standalone/sdk-check.js +4 -1
  52. package/dist/standalone/spk.d.ts +3 -0
  53. package/dist/standalone/spk.js +10 -3
  54. package/dist/validate.js +2 -0
  55. package/package.json +5 -5
  56. package/skills/SKILL.md +3 -1
  57. package/skills/reference/create.md +5 -1
  58. package/skills/reference/identity.md +1 -1
  59. package/skills/reference/lifecycle.md +110 -0
  60. package/skills/reference/portal.md +12 -0
  61. package/dist/authorization-sdk.d.ts +0 -6
  62. package/dist/authorization-sdk.js +0 -6
  63. package/dist/standalone/native-authorization-window.d.ts +0 -2
  64. package/dist/standalone/native-authorization-window.js +0 -36
  65. package/dist/standalone/native-root-window.d.ts +0 -29
  66. package/dist/standalone/native-root-window.js +0 -307
package/CHANGELOG.md CHANGED
@@ -1,1705 +1,268 @@
1
1
  # Changelog
2
2
 
3
- ## 0.10.2 (2026-10-14)
4
-
5
- Builds with SDK 0.10.2 and templates 0.10.2: `init` writes SDK v0.10.2 (Go
6
- module, Rust crate tag and `backend.sdk`) and `@synopackageland/sdk` 0.10.2.
7
- The executor digest table adds SDK 0.10.2 and keeps 0.10.0 and 0.10.1. SDK
8
- 0.10.2's executor changed (it adds `check-policy`), so an App rebuilt on it
9
- carries a new executor digest; when its policy digest does not change, the
10
- upgrade puts the new executor in place without approval. The lifecycle
11
- digests equal 0.10.1.
12
-
13
- 繁中摘要:使用 SDK 0.10.2 與 templates 0.10.2;digest 表新增 SDK 0.10.2(保留
14
- 0.10.0、0.10.1)。executor 變更(新增 check-policy),policy digest 不變的 App
15
- 升級時自動換 executor,不需重新核准;lifecycle digest 與 0.10.1 相同。
16
-
17
- - `check` and `build` have the App's SDK executor validate the executor policy
18
- (`executor check-policy`, SDK 0.10.2 and later, which `init` now writes; built for this host from the
19
- App's SDK version, or from `SYNOPKGLAND_SDK_DIR`). A policy the executor
20
- would refuse fails with `EXECUTOR_POLICY_REJECTED`, the executor's reason and
21
- the dsm-package.yaml part to fix, instead of failing only at approval on the
22
- NAS. Apps without delegate or rootOps, and SDKs before check-policy, are not
23
- checked.
24
- - Fix: `inspect` reports an SDK it cannot download as `SDK_DOWNLOAD_FAILED`
25
- with the GOPRIVATE hint. It was `SPK_INSPECTION_FAILED` ("Executor inventory
26
- invalid", "do not install an SPK that fails inspection"), which blamed a
27
- valid SPK for the machine's Go setup.
28
- - Fix: a DSM window (and a Portal or standalone launch) passes the App
29
- `ctx.displayName` in the DSM language: the translated `i18n.info.<locale>.displayname`,
30
- the name DSM shows in the window title, else the English `displayname`. It
31
- was always the English name, so a narrow-window header and the title bar
32
- disagreed under a translated DSM.
33
-
34
- ## 0.10.1 (2026-10-13)
35
-
36
- Builds with SDK 0.10.1 and templates 0.10.1: `init` writes SDK v0.10.1 (Go
37
- module, Rust crate tag and `backend.sdk`) and `@synopackageland/sdk` 0.10.1.
38
- The executor digest table adds SDK 0.10.1 and keeps 0.10.0. SDK 0.10.1's
39
- executor changed, so an App rebuilt on it carries a new executor digest and
40
- its pins are approved again; the lifecycle digests equal 0.10.0. The bridge
41
- manifest schema is synced from the SDK (`capabilities.root` also serves
42
- `executor.status` and `executor.control`).
43
-
44
- 繁中摘要:使用 SDK 0.10.1 與 templates 0.10.1;digest 表新增 SDK 0.10.1(保留
45
- 0.10.0)。executor 變更,改用 SDK 0.10.1 重建的 App 要重新核准 executor digest,
46
- lifecycle digest 與 0.10.0 相同。
47
-
48
- - A Python, Node.js or Rust App with root-ops and a Vue UI borrows
49
- `native-go-root-ops/frontend`, as a Go App gets it from its overlay: its
50
- `ExecutorGate.vue` holds the App's pages until an administrator approves
51
- the executor (templates 0.10.1).
52
- - Fix: in a DSM window with root operations (`capabilities.rootOps`, and so a
53
- `portal` App), the App root now fills the window body below the root panels.
54
- It had no height, so an App root asking for `height:100%` collapsed to its
55
- content: a loading overlay covered only the top of the window, and a dialog
56
- centred on the App root was placed above the window. Windows without root
57
- operations are unchanged.
58
-
59
- ## 0.10.0 (2026-10-13)
60
-
61
- Builds with SDK 0.10.0 and templates 0.10.0: `init` writes SDK v0.10.0 (Go
62
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.0 and
63
- `@synopackageland/ui` 0.9.4, and the Vue templates use the SDK's `AppShell`.
64
- CLI 0.10 builds only SDK 0.10.x: the executor digest table records SDK
65
- 0.10.0 and no longer lists 0.9.x, so an App on SDK 0.9.x is refused
66
- (`SDK_UNSUPPORTED`) and stays on CLI 0.9.7. SDK 0.10.0's executor changed, so
67
- a rebuilt App carries new executor and lifecycle digests and its pins are
68
- approved again; the policy digest changes only for a policy that uses root
69
- program settings, jobs, `stop` or `state`.
70
-
71
- 繁中摘要:使用 SDK 0.10.0 與 templates 0.10.0(UI 0.9.4),Vue 範本改用 SDK 的
72
- `AppShell`;新增 Python、Node、Rust App(bridge runtime)、root programs 設定、
73
- `ui.window`、`backend.timeout_seconds`(預設 60 秒),CLI 輸出全改英文。CLI 0.10
74
- 只接受 SDK 0.10.x,digest 表移除 0.9.x;executor 變更,重建的 App 要重新核准
75
- executor 與 lifecycle digest。
76
-
77
- - Breaking: every CLI message is English. Error descriptions and suggestions,
78
- `--help` text, and check, inspect, build and deploy output no longer carry
79
- Chinese. Only the multilingual data shipped to DSM end users keeps its
80
- translations: the install/upgrade/uninstall wizard copy, the App UI catalogs
81
- and the INFO `displayname`/`description` translations. A test now fails on
82
- CJK in any other `src/` file.
83
- - Behavior change: every App's backend request deadline is 60 seconds instead
84
- of the SDK's 20. `bridge.json` always carries `timeout_seconds`, so the
85
- runtime's deadline and the `deadline_ms` it sends the App follow it; a Go
86
- CGI gets it as `-X main.requestTimeout` and keeps the SDK's 20 seconds
87
- until its main reads that variable into `api.Timeout`. The DSM window sets
88
- `ctx.requestTimeout` to the deadline and gives up two seconds past it (62
89
- seconds instead of 22), also for a prebuilt binary CGI.
90
- - `backend.timeout_seconds` (1–3600) sets another deadline for go, python,
91
- node and rust backends. A Go main that does not read `requestTimeout` into
92
- `api.Timeout` is refused (`STANDALONE_TIMEOUT_UNSUPPORTED`); a binary
93
- backend refuses it.
94
- - `ui.window: {width, height, minWidth, minHeight}` sets the DSM window's
95
- size; omitted fields keep 960×640, at least 700×420, and a standalone
96
- launch keeps its 320 px minimum width. Sizes outside 320–3840 × 240–2160,
97
- or a minimum larger than the size, are refused
98
- (`STANDALONE_UI_WINDOW_INVALID`).
99
- - The generated DSM window knows a standalone (Portal) launch,
100
- `_S("standalone") === true`: its minimum width drops from 700 to 320 px so
101
- a phone can show it, and `mount` gets `ctx.fullPage` and `ctx.appIcon` (the
102
- package's 64 px window icon) for a full-page layout and brand.
103
- - `init -t native-python | native-node | native-rust` creates the native-go
104
- App with a Python, Node.js (TypeScript) or Rust backend: the Go template's
105
- UI, frontend and root program, the language's capability overlays for
106
- `--with jobs,root-ops,delegate` (jobs also declares `files: session`, which
107
- its example reads; delegate opens `app.status` and `report.save` to
108
- signed-in users; Rust lists the added methods in `backend.methods`), and the
109
- App SDK its tests import in `backend/vendor`. `--shared-folder` stays
110
- native-go only. Capability overlays carry their permission rules.
111
- - Breaking: `backend.kind: rust` (the Rust full SDK) is removed, and
112
- `init -t native-rust` now creates the thin Rust App behind the bridge
113
- runtime instead.
114
- - `backend.runtime: python | node | rust` builds an App in another language
115
- behind the SDK's Go runtime (spec/bridge/v1). `build` compiles
116
- `bridge/cmd/runtime` of the App's SDK release as `ui/cgi/api.cgi` with the
117
- sha256 of `bridge.json` and `permissions.json` bound, and ships the App in
118
- `target/app`: Python with the SDK in `vendor/`, Node with the SDK in
119
- `node_modules/@synopackageland/app` and an `install_dep_packages` on
120
- Package Center's Node.js, Rust cross-compiled with `cargo zigbuild`.
121
- Methods come from `backend.methods` or the App's `--describe`. `inspect`
122
- checks the manifest schema, the digests, the entry, that the App is not
123
- under `ui/`, and the Node dependencies. The `bridge` gate builds all three
124
- for every architecture.
125
- - Bridge Apps may declare `jobs`, `rootOps`, `portal` and an
126
- `until-revoked` `delegate` for `files.moveFromStaging`, plus
127
- `backend.transfers` and `backend.max_upload_bytes`; `bridge.json` carries
128
- them. With jobs or delegate the runtime is also installed as
129
- `target/background/worker` and DSM runs it with `--worker` as the package
130
- account. `inspect` refuses delegate digests that are not the SPK
131
- executor's. The Node SDK is copied as its `package.json` and `dist/`, so
132
- the SDK checkout must be built.
133
- - `root` in dsm-package.yaml configures root programs for the executor's
134
- root programs, root jobs, package stop step and root state directory:
135
- `programs.<name>` gives a params `schema` and `run` or `job` limits,
136
- `kind: binary` takes `root/<arch>/<name>` (an ELF for each architecture,
137
- so the policy digest differs per architecture), `stop` names the program
138
- the executor runs at package stop and upgrade, and `state: true` asks for
139
- the root state directory. Programs without settings keep the bare digest,
140
- so existing policies and their digests do not change. Jobs add
141
- `root.start` and the `root.jobs` operations. A build whose SDK executor
142
- predates these refuses them (`EXECUTOR_ROOT_PROGRAMS_UNSUPPORTED`).
143
- - `preupgrade` stops the executor with `executor/lifecycle stop --upgrade`;
144
- an installed helper without the flag gets a plain stop.
145
-
146
- - The DSM window icons are named after their content,
147
- `images/app_<hash>_<size>.png`, and `ui/config` points at that name. DSM and
148
- the browser cache an App icon by its URL, so after an icon change DSM kept
149
- showing the old one (the blue square in t-0391) until the cache expired; a
150
- new icon now has a new URL, and an unchanged icon keeps its cached one. DSM
151
- 7.4 passes the config's icon path, with `{0}` for the size, to
152
- `SYNO.Core.Synohdpack getHDIcon` for every `webman/3rdparty/` App, so any
153
- file name works.
154
- - `ctx.call(method, params, {signal})` aborts its fetch when the signal does,
155
- so a page the user leaves really stops its request, and rejects
156
- `{code: "request_canceled"}`. It also gives up after the SDK's
157
- `clientTimeout(ctx.requestTimeout)`, two seconds past the backend deadline
158
- (the backend deadline unless the App sets `ctx.requestTimeout` in ms, read
159
- on every call), aborts the fetch and rejects `{code: "request_timeout"}`.
160
- Before, a call waited as long as the browser did. The portal and root
161
- panels use the same call.
162
- - preuninst waits for the App's uninstall hook by looking it up in `/proc`
163
- instead of `kill -0`. DSM's bash logs a failed builtin to syslog even with
164
- `2>/dev/null`, so every uninstall with a hook logged `BASH_ERR ... kill: No
165
- such process` twice (t-0383 B8). The wait, the timeout and the result are
166
- unchanged; a host without `/proc` still uses `kill -0`.
167
- - preuninst stops an uninstall hook past its timeout, and prestart waits for
168
- a stale package user manager to exit, without a `kill` that can fail: the
169
- hook's process group and the manager are looked up in `/proc` first, so
170
- neither logs `BASH_ERR ... kill: No such process` once the process has
171
- exited.
172
- - The Application Portal panel's Italian instructions use the infinitive like
173
- DSM ("Riprovare o usare il Pannello di controllo."), the same text as the
174
- SDK's `i18n/portal` catalogs.
175
- - `ctx.portal.status()` waits for a register or unregister in progress to
176
- end, whatever its result, like the SDK's `createPortalAccess`. Before, a
177
- status read during a slow change could show "The Application Portal entry
178
- was not fully removed".
179
-
180
- ## 0.9.7 (2026-10-13)
181
-
182
- Builds with SDK 0.9.8 and templates 0.9.7: `init` writes SDK v0.9.8 (Go
183
- module and Rust crate tag), `@synopackageland/sdk` 0.9.8 and
184
- `@synopackageland/ui` 0.9.2. SDK 0.9.8 fixes `BackgroundPanel`, whose controls
185
- to choose a file and submit it were hidden on DSM. The executor digest table
186
- records SDK 0.9.8 with the same executor and lifecycle digests as 0.9.7, and
187
- the policy digest does not change, so a rebuilt App keeps the pins it was
188
- approved with.
189
-
190
- 繁中摘要:使用 SDK 0.9.8 與 templates 0.9.7(UI 0.9.2)。SDK 0.9.8 修正
191
- `BackgroundPanel` 在 DSM 上看不到選檔與送出按鈕。digest 表加入 SDK 0.9.8,
192
- executor 與 lifecycle digest 與 0.9.7 相同,policy digest 也不變,重建的 App
193
- 沿用原本核准的 pins。
194
-
195
- ## 0.9.6 (2026-10-12)
196
-
197
- Builds with SDK 0.9.7 and templates 0.9.6: `init` writes SDK v0.9.7 (Go
198
- module and Rust crate tag), `@synopackageland/sdk` 0.9.7 and
199
- `@synopackageland/ui` 0.9.2. The executor digest table records SDK 0.9.7,
200
- whose executor and lifecycle binary changed, so a rebuilt App carries new
201
- executor digests. The policy digest changes only for an App that declares
202
- `uninstall.purge.root`, and such an App asks for approval again after the
203
- upgrade; `bg.jobs.pause` and `bg.jobs.resume` reach the CGI route table, not
204
- the policy. Whether new executor digests alone ask for approval again has not
205
- been checked on a DSM yet.
206
-
207
- 繁中摘要:使用 SDK 0.9.7 與 templates 0.9.6(UI 0.9.2)。executor digest 表加入
208
- SDK 0.9.7;executor 與 lifecycle 都有變更,重建的 App 會帶新的 executor digest。
209
- 只有宣告 `uninstall.purge.root` 的 App 的 policy digest 會變,升級後需要重新核准;
210
- `bg.jobs.pause`、`bg.jobs.resume` 只進 CGI route 表,不影響 policy。只換 executor
211
- digest 時是否需要重新核准,尚未在 DSM 上驗證。新增解除安裝 hook(`uninstall.purge`)。
212
-
213
- - Uninstall hook: `uninstall.purge` in dsm-package.yaml (`exec`, `args`,
214
- `env`, `timeout` 2..120 s, optional `root`) names the App's own command,
215
- and `uninstall.state.items` lists the state it can delete, each with an
216
- `uninstall.state.<key>` text. On a real uninstall preuninst stops the
217
- worker and the daemon, runs the hook as the package account with
218
- `SYNOPKGLAND_DELETE_STATE` and `SYNOPKGLAND_REMOVE_DATA`, then asks the
219
- executor to purge; a failing or timed-out hook is logged and never blocks
220
- the uninstall, and an upgrade skips it. The wizard gains an unchecked
221
- "delete state" box after the item texts. `purge.root` names a rootOps
222
- script the executor runs as root only while its approval stands; the
223
- policy's `purge` member records it, so such an App asks for approval
224
- again. Build refuses a hook outside the package or one that tries to
225
- become root. inspect checks an executor SPK's preuninst and wizard against
226
- the hook recorded in sdk-package.json, and that record's root step against
227
- the policy's `purge`.
228
- - `init` and `add` change only the dsm-package.yaml keys they set
229
- (`capabilities`, `portal`, `os_min_ver`); the template's comments, such as
230
- the commented `uninstall` example, and its formatting stay as written.
231
- - With `SYNOPKGLAND_SDK_DIR`, init also drops the released SDK from the
232
- frontend's package-lock.json, so npm installs the local ts package.
233
-
234
- ## 0.9.5 (2026-10-12)
235
-
236
- Builds with SDK 0.9.6 and templates 0.9.5: `init` writes SDK v0.9.6,
237
- `@synopackageland/sdk` 0.9.6 and `@synopackageland/ui` 0.9.2. The executor
238
- digest table records SDK 0.9.6, whose executor registers the portal entry
239
- and checks a grant's background work from the browser address it recorded,
240
- so a rebuilt App carries new executor digests. The policy digest changes
241
- only for an App whose executor registers its portal entry (a portal and
242
- os_min_ver below 7.2): after the upgrade it asks for approval again. Other
243
- Apps keep their policy digest.
244
-
245
- 繁中摘要:使用 SDK 0.9.6 與 templates 0.9.5(UI 0.9.2)。executor digest 表加入
246
- SDK 0.9.6,重建的 App 會帶新的 executor digest。只有由 executor 註冊入口的
247
- portal App(宣告 portal 且 os_min_ver 低於 7.2)policy digest 會變,升級後需重新
248
- 核准;其他 App 的 policy digest 不變。DSM 7.1 起可用 portal(executor 模式)。
249
-
250
- - The generated DSM window stays inside the desktop: its width is at most the
251
- viewport's and its height at most the viewport's less the DSM taskbar. This
252
- applies to the default 960×640 and to the size DSM restores from
253
- `restoreSizePos`, which may have been saved on a larger screen. Position and
254
- maximized state are restored as before.
255
- - Native root authorization (`createRootAccess` from
256
- `@synopackageland/cli/authorization-sdk`): the first `run` after an `authorize`
257
- runs the task that `authorize` just read back as exactly ours, instead of
258
- reading the TaskScheduler list and task again. Any later run, a repair, and
259
- a run after `remove` still read DSM first; `authorize` still reads the task
260
- back after creating it and checks its identity and script.
261
- - Native root authorization polls the task history every 0.5 s, then at most
262
- once a second (was up to every 2 s), so a finished root task is seen up to
263
- 1 s sooner.
264
- - A portal works from DSM 7.1-42661 on. Below 7.2-64570 (executor mode) the
265
- SPK carries no web-config resource, `etc/portal.*`, alias wizard step or
266
- alias lifecycle; the executor policy carries `portal: {application, alias}`
267
- and the `portal.register` / `portal.unregister` operations, and the CGI is
268
- built with `-X main.portal=executor`. A package name or displayname the
269
- executor would refuse fails with `STANDALONE_PORTAL_ENTRY_INVALID`; below
270
- 7.1 `STANDALONE_PORTAL_MIN_DSM` still refuses the portal, and `add portal`
271
- raises os_min_ver only to 7.1-42661. inspect reports the policy's entry
272
- and flags an INFO os_min_ver below 7.1. Needs templates 0.9.5 and SDK 0.9.6.
273
- - `ctx.portal` in executor mode: `status()` carries `entry`, administrators
274
- get `register()` and `unregister()`, and the plain panel offers "Enable
275
- entry", "Remove entry" or the alias form by entry state. New texts in nine
276
- languages, including `PORTAL_FAILED`. The panel's Korean texts use the
277
- formal -십시오 register.
278
- - The uninstall wizard and the cleanup list of an executor-mode portal App
279
- warn that a stopped or never-approved executor leaves the entry, and give
280
- the root cleanup commands.
281
- - native-layout: `portal: {}` or `{alias}` (DSM 7.1-42661+) writes
282
- `synopkgland_portal_alias` into INFO for the product's own root helper;
283
- inspect checks it against `native-layout.json`.
284
- - DSM's web-config portal registration now needs `os_min_ver` 7.2-64570 or
285
- later instead of 7.3.2-86009. DSM 7.1's package manager marks the web-config
286
- worker Synology-only and refuses it in an unsigned SPK (error 320); synopkg
287
- from DSM 7.2 on drops that mark, the only change to its worker table. The
288
- 7.2 floor comes from that source and is not yet verified on a 7.2 NAS.
289
- inspect flags a web-config portal SPK whose INFO os_min_ver is lower, with
290
- this reason.
291
-
292
- ## 0.9.4 (2026-10-12)
293
-
294
- Builds with SDK 0.9.5 and templates 0.9.4: `init` writes SDK v0.9.5 and
295
- `@synopackageland/sdk` 0.9.5, and the Vue App's `AppRoot` lets Dialogs leave
296
- a DSM window. The executor digest table records SDK 0.9.5, whose executor
297
- equals 0.9.4's; the policy digest stays the same, so an installed App needs
298
- no new approval. AI guidance for DSM-window Dialogs matches the Vue App.
299
-
300
- 繁中摘要:使用 SDK 0.9.5 與 templates 0.9.4。SDK 0.9.5 只改 TypeScript(NAS
301
- picker 改用 UI 0.9.1 元件),executor digest 與 0.9.4 相同,policy digest 不變,
302
- 已安裝的 App 不需重新核准。ui-vue 與 ui-json 指引改為在 DSM 視窗內給 `AppRoot`
303
- `dialog-bounds="viewport"`,並把 `activate` 接到 `ctx.host.activateWindow()`:
304
- Dialog 可拖出 App 視窗,點 Dialog 會把視窗帶到最前;600px 以下為底部 sheet。
305
-
306
- - `ui-vue` and `ui-json`: in a DSM window (`ctx.host.platform().host === 'dsm'`),
307
- give `AppRoot` `dialog-bounds="viewport"` and connect its `activate` event to
308
- `ctx.host.activateWindow()`, so Dialogs can be dragged out of the window and
309
- pressing one brings the window to the front. Below 600px they are bottom
310
- sheets. Standalone web, Portal and previews keep Dialogs inside the App.
311
- - A host-window test pins that the mounted App's `activateWindow` fronts only
312
- its own DSM window.
313
-
314
- ## 0.9.3 (2026-10-12)
315
-
316
- Builds with SDK 0.9.4 and templates 0.9.3: `init` writes SDK v0.9.4 and
317
- `@synopackageland/sdk` 0.9.4. The executor digest table records SDK 0.9.4,
318
- whose executor asks DSM's native `SLIBAppPrivUserHas` for App Privileges
319
- instead of the `SYNO.Core.AppPriv.App` preview. An App rebuilt with SDK 0.9.4
320
- gets the new executor; the policy digest stays the same, so an installed App
321
- needs no new approval.
322
-
323
- 繁中摘要:使用 SDK 0.9.4 與 templates 0.9.3。executor 改以 DSM 原生
324
- `SLIBAppPrivUserHas` 判斷應用程式權限,digest 表加入 0.9.4;policy digest 不變,
325
- 重新建置後已安裝的 App 不需重新核准。限定 IP 的應用程式權限規則改為依瀏覽器位址
326
- 判斷;自行組 `auth` 的呼叫端必須帶 `remote_addr`,否則 executor 回 `APP_DENIED`。
327
-
328
- - Behavior change: an Application Privileges rule limited to IP addresses
329
- applies to the browser's address; with SDK 0.9.3 or earlier the executor
330
- refused every request under such a rule. Work no browser asked for is
331
- checked as coming from 127.0.0.1. The generated CGI sends the address
332
- through `api.DSMSession`; code that builds the executor `auth` itself must
333
- add `remote_addr` or the executor refuses with `APP_DENIED`.
334
- - The fake-DSM executor flow tests host the 0.9.4 executor's embedded bridge
335
- and check the online grant against the browser's address and the offline
336
- write against 127.0.0.1.
337
-
338
- ## 0.9.2 (2026-10-12)
339
-
340
- Builds with SDK 0.9.3 and templates 0.9.2: `init` writes SDK v0.9.3 and
341
- `@synopackageland/sdk` 0.9.3. The executor digest table records SDK 0.9.2 and
342
- 0.9.3 (the same digests; 0.9.3 changes only translations). An App rebuilt
343
- with SDK 0.9.2 or 0.9.3 gets the new executor; the policy digest stays the
344
- same, so an installed App needs no new approval.
345
-
346
- 繁中摘要:`check` 與 `build` 的錯誤訊息每個錯誤碼只出現一次;`build --json` 的 SDK
347
- 下載失敗附上 GOPRIVATE 提示;有 executor 並宣告 `install.sharedFolder` 的 App 通過
348
- `inspect` 的 postinst 檢查。重新安裝後的核准只需輸入一次密碼:共用資料夾讀回等 40 s,
349
- 沒到達 executor 的選擇任務在同一次核准內再跑一次,啟動後的健康檢查等 30 s。
350
-
351
- - `check` and `build` name each error code once. 0.9.1 printed the first code
352
- twice, for example
353
- `[STANDALONE_PORTAL_REQUIRES_EXECUTOR] [STANDALONE_PORTAL_REQUIRES_EXECUTOR] …`.
354
- - A `--json` error without its own hint takes the registry suggestion of its
355
- code. `build --json` now answers `SDK_DOWNLOAD_FAILED` with the GOPRIVATE and
356
- git-credential step instead of the generic hint.
357
- - `inspect` accepts an App with `capabilities.rootOps` or `delegate` that
358
- declares `install.sharedFolder`. Its `postinst` and `postupgrade` start the
359
- executor before the shared-folder script, and 0.9.1 reported them as not
360
- matching.
361
- - Re-approval after a reinstall takes one password submission for both the
362
- executor and an install-selected share. The DSM window now reads the share
363
- selection back for 40 s instead of 20 s, runs a selection task that missed
364
- the restarting executor once more without a new confirmation, and waits
365
- 30 s instead of 10 s for a started executor before withdrawing the tasks an
366
- approval created.
367
-
368
- ## 0.9.1 (2026-10-12)
369
-
370
- Fixes from the 0.9.0 acceptance on DSM (t-0294, t-0290, t-0292).
371
-
372
- 繁中摘要:`build` 重複 `--arch` 時每個架構都建置;SDK 下載失敗回報
373
- `SDK_DOWNLOAD_FAILED`(不再包成 `STANDALONE_API_PERMISSIONS_INVALID`),`check`
374
- 顯示每個錯誤碼自己的建議;`status` 列出 portal;Vue App 只在 SDK 的
375
- ExecutorSettings 核准,plain UI 的 DSM 視窗面板改為有樣式、跟隨 DSM 語言並在面板內
376
- 輸入密碼。更正 0.9.0 的核准說明:policy 不變的升級不需重新核准,只有
377
- install-selected 的 scope 需要管理員確認一次。記錄 SDK 0.9.1 executor digests(只換
378
- executor、policy digest 不變,免重新核准)。App 分兩層:有 executor(rootOps 或
379
- delegate)的 App 解除安裝時自動移除應用程式權限規則,宣告 portal 時再 reload nginx,
380
- 精靈與 cleanup 清單只把它們列為自動清除失敗時的後備;無升權 App 任何階段都不用 root,
381
- 應用程式權限規則仍由管理員手動移除。破壞性變更:portal 只限 executor 層級,無升權
382
- native App 以 `STANDALONE_PORTAL_REQUIRES_EXECUTOR` 拒絕,Compose App 以
383
- `CONTRACT_PORTAL_REQUIRES_EXECUTOR` 拒絕。並附 0.9.0 安裝殘留的清理步驟。
384
-
385
- ### Build and check
386
-
387
- - `build --arch x86_64 --arch aarch64` builds every family named; 0.9.0 built
388
- only the last one. Repeated values and comma lists combine.
389
- - An SDK version Go cannot fetch is reported as `SDK_DOWNLOAD_FAILED` with the
390
- GOPRIVATE and git-credential hint. 0.9.0 wrapped it as
391
- `STANDALONE_API_PERMISSIONS_INVALID`.
392
- - `check` shows the registry suggestion of each error code. It no longer
393
- replaces every `dsm-package.yaml` error's suggestion with "fix the schema 2
394
- fields".
395
- - `STANDALONE_ENTRY_UNSUPPORTED` names the `main.go` of the project's
396
- `backend.main`, not always `cmd/app/main.go`.
397
-
398
- ### Status
399
-
400
- - `status` lists `portal` for an App with `portal: {}`. It reads the App's
401
- `portal.status`: `ready` with the URL when an administrator set an alias,
402
- `needs-admin-approval` when none is set, and `blocked` before install or
403
- when the status cannot be read.
404
-
405
- ### DSM window approval
406
-
407
- - A Vue App approves the executor only in the SDK's `ExecutorSettings`. The
408
- DSM window no longer adds its own approval panel to a Vue App with
409
- `rootOps` or a fixed delegate scope. That panel used unstyled English
410
- buttons, and its password prompt was appended below the App, so approving
411
- from it never reached PasswordConfirm.
412
- - A plain-UI App keeps the window panel. It now uses the panel styles, shows
413
- its text in the DSM language (the nine SDK locales), and asks for the
414
- administrator password inside the panel. The password goes to
415
- `authorize` the same way as in `ExecutorSettings`.
416
- - Breaking: `confirmPassword` without a password fails with
417
- `password_required` and sends nothing; the masked form it appended is gone.
418
-
419
- ### Portal needs the executor tier (breaking)
420
-
421
- - DSM removes a portal App's Login Portal alias at uninstall but does not
422
- reload nginx (DSM 7.4, t-0294), so `/<alias>/` kept opening a DSM page.
423
- Only root can reload it, so a `portal` now needs `capabilities.rootOps` or
424
- `capabilities.delegate`. A no-privilege native App with `portal` fails
425
- `check`/`build` with `STANDALONE_PORTAL_REQUIRES_EXECUTOR`; `add portal`
426
- refuses it unless the same call adds `root-ops` or `delegate`; `inspect`
427
- flags such an SPK with `PORTAL_REQUIRES_EXECUTOR`.
428
- - A Compose App has no executor: `integration.web.portal` now always fails
429
- with `CONTRACT_PORTAL_REQUIRES_EXECUTOR` and the schema no longer describes
430
- its shape. `CONTRACT_PORTAL_INVALID`, `CONTRACT_PORTAL_UNSUPPORTED` and
431
- `CONTRACT_PORTAL_MIN_DSM` are gone, and `inspect` reports a Compose SPK's
432
- `web-config` as an unknown resource.
433
- - Fix: add `root-ops` or `delegate`, or remove the portal and let an
434
- administrator set up a reverse proxy in DSM.
435
-
436
- ### Uninstall
437
-
438
- - An App with the executor (rootOps or delegate) cleans up after DSM itself:
439
- SDK 0.9.1's root purge deletes the App's rules in Control Panel >
440
- Application Privileges and, for a portal App, reloads nginx once DSM has
441
- removed the alias. The uninstall wizard says so in the nine languages, and
442
- `status`/`inspect` list both only as fallbacks: "Only if automatic cleanup
443
- failed after uninstall; see the system log entry synopkgland-executor."
444
- - A no-privilege App never uses root, not even at uninstall. Its wizard keeps
445
- saying that DSM keeps the App's rules in Control Panel > Application
446
- Privileges, that a later install under the same name applies them again,
447
- and that an administrator can remove them; the cleanup list keeps that
448
- item. It never mentions a Login Portal alias, because it cannot declare one.
449
- A test builds plain, files, jobs, identity and `entry: app-privilege` SPKs
450
- and checks they run as the package account, ship no executor or root
451
- tasks, and that their scripts never call synowebapi, nginx or a system
452
- unit.
3
+ ## 0.10.4 (2026-10-14)
4
+
5
+ Builds with SDK 0.10.4 and templates 0.10.4: `init` writes SDK v0.10.4 (Go
6
+ module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.4 and
7
+ `@synopackageland/ui` 0.9.6. `check` and `build` still accept SDK 0.10.3 or
8
+ later: nothing in this CLI needs 0.10.4, and an App on 0.10.3 never sees the
9
+ stop and start codes the window now repeats. The executor digest table adds
10
+ SDK 0.10.4, whose executor and lifecycle digests equal 0.10.3, so an App
11
+ rebuilt on it keeps its approval unless its own root programs change. The
12
+ gate refuses a stale SDK `ts/dist` and builds the executor flow's race binary
13
+ before its timeout.
14
+
15
+ 繁中摘要:使用 SDK 0.10.4 與 templates 0.10.4(UI 0.9.6);`check`/`build` 仍接受 SDK
16
+ 0.10.3 以上。`ctx.call` 自動重試套件停止/啟動中的碼;非管理員的
17
+ `ctx.executor.status()` 可讀到已核准的 executor。digest 表新增 SDK 0.10.4(與 0.10.3
18
+ 相同),重建的 App 不需重新核准(除非自己的 root 程式改變)。
19
+
20
+ - `ctx.call` repeats a call the package refused with `APP_STOPPED`,
21
+ `APP_STARTING` or `EXECUTOR_STARTING` (the executor op was not sent): after
22
+ 0.5 s, doubling to at most 8 s, for 30 s of waiting in all, so an App comes
23
+ back by itself once the package has started. Other codes reject at once; past
24
+ the budget the last refusal rejects; leaving the page cancels the wait.
25
+ 繁中:套件停止/啟動中時 `ctx.call` 自動有界重試(最多等 30 s)。
26
+ - The window approval of an upgrade (outdated tasks) makes 11 scheduler calls
27
+ instead of 12: the outdated boot task is deleted by the approval's own first
28
+ EventScheduler get, made moments before the password, instead of a second
29
+ get (2.9 s on vt-nas in t-0484). The control task is still read again by id
30
+ before its delete (spec/scheduler/v1). A fresh approval stays at 8.
31
+ 繁中:升級核准的排程呼叫由 12 降為 11(boot 任務不再於刪除前重讀)。
32
+ - A scope approval reads the executor's selection back every second at most
33
+ (it backed off to 2 s), so the window sees the landed scope within a second
34
+ of `scope.confirm` (the Dialog closed 4.8 s after it in t-0482). Removing
35
+ the temporary selection task still lists Task Scheduler first, as
36
+ spec/scheduler/v1 requires of a delete.
37
+ 繁中:範圍核准讀回間隔上限 2 s → 1 s,confirmed 後 1 s 內看到結果。
38
+ - `ctx.executor.status()` for a user who is not an administrator asks the
39
+ App's `executor.status` (open to every signed-in user) instead of refusing
40
+ before any request: the pinned, running executor reads `ready`; anything
41
+ else stays `blocked` with `admin_required`. Task Scheduler is still read only
42
+ for administrators. A user no longer sees an approved executor as waiting
43
+ for approval (t-0482 F7).
44
+ 繁中:非管理員的 `ctx.executor.status()` 會向 App 的 `executor.status` 詢問,已核准且
45
+ 執行中即為 `ready`,不再一律回 `admin_required`。
46
+
47
+ ## 0.10.3 (2026-10-14)
48
+
49
+ Builds with SDK 0.10.3 and templates 0.10.3: `init` writes SDK v0.10.3 (Go
50
+ module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.3 and
51
+ `@synopackageland/ui` 0.9.5. **Incompatible:** `check` and `build` need SDK
52
+ 0.10.3 or later. The executor digest table adds SDK 0.10.3 and keeps 0.10.0
53
+ to 0.10.2, so `inspect` still recognizes their SPKs. SDK 0.10.3's executor
54
+ records the package version at every start, so an App rebuilt on it carries
55
+ a new executor digest; when its policy digest does not change, the upgrade
56
+ puts the new executor in place without approval. An App that declares
57
+ `root.start` changes its policy and is approved again. The lifecycle digests
58
+ equal 0.10.2.
59
+
60
+ 繁中摘要:使用 SDK 0.10.3 與 templates 0.10.3(UI 0.9.5);`check`/`build` 需要 SDK
61
+ 0.10.3 以上(不相容)。digest 表新增 SDK 0.10.3(保留 0.10.0~0.10.2)。policy digest
62
+ 不變的 App 升級時自動換 executor;宣告 `root.start` 的 App 需要重新核准。lifecycle
63
+ digest 與 0.10.2 相同。
64
+
65
+ ### Added
66
+
67
+ - `root.start` in dsm-package.yaml names a `root/` program as the executor's start step: the
68
+ executor runs it as root once at the first start after each install or upgrade (`install`,
69
+ or `upgrade` and the previous version), only while the approval is live, for at most 60
70
+ seconds, and a failure never blocks the start. `build` writes it into the policy as `start`;
71
+ `check` refuses a name outside `root/` and a program that also declares `run` or `job`
72
+ (`STANDALONE_ROOT_PROGRAMS_INVALID`); the SDK executor's `check-policy` checks the rest.
73
+ `inspect` lists the root steps. Declaring it changes the policy digest, so the App needs
74
+ approval again; Apps without it keep their policy.
75
+
76
+ 繁中摘要:`root.start` 指定一個 root 程式,executor 在每次安裝或升級後第一次啟動時以 root 執行一次;
77
+ 失敗只記 log、不擋啟動。宣告後 policy digest 改變需重新核准,沒有宣告的 App 不受影響。
78
+
79
+ - Lifecycle scripts: a project's `dsm-scripts/<DSM script name>` (`preinst`, `postinst`,
80
+ `preupgrade`, `postupgrade`, `preuninst`, `postuninst`, `start-stop-status`) is packed as
81
+ `scripts/app/<name>` and called from the generated script of the same name: after the SDK
82
+ steps, and before them for `preupgrade`, `preuninst` and `stop`. It runs as the package
83
+ account with DSM's arguments, environment and exit codes; a failed App `start` stops the SDK
84
+ again. Native and Compose projects. `check` refuses other names (`APP_SCRIPT_UNKNOWN`),
85
+ non-executable files and files without `#!`; `inspect` lists the App scripts. Projects
86
+ without `dsm-scripts/` build unchanged. Guide: `synopkgland skill show lifecycle`.
87
+
88
+ 繁中摘要:專案 `dsm-scripts/` 下以 DSM 原生檔名放的腳本會被打包,並在 CLI 產生的同名腳本中於
89
+ SDK 步驟之後(preupgrade/preuninst/stop 在之前)以套件帳號執行;沒有該資料夾時行為不變。
90
+
91
+ - Login Portal style: a portal App's launcher now carries DSM's `loginStyle` and `loginIcon`,
92
+ so Control Panel > Login Portal > Applications shows a thumbnail instead of a blank box, and
93
+ the alias login page gets a wallpaper, a button colour and the App icon. The CLI ships a
94
+ neutral default (`assets/login-style/`); `login_style` in dsm-package.yaml overrides
95
+ `color`, `wallpaper` with `wallpaper_thumbnail`, and `title`. `check` validates the images
96
+ (`STANDALONE_LOGIN_STYLE_INVALID`, `STANDALONE_LOGIN_STYLE_IMAGE_INVALID`) and prints the
97
+ style; `inspect` prints it and flags a portal SPK without one (`LOGIN_STYLE_MISSING`).
98
+ Rebuilding an existing portal App with this CLI adds the default; no change to the project.
99
+
100
+ 繁中摘要:有 portal 的 App 重新 build 後會自動帶入預設登入樣式,「登入入口 › 應用程式」縮圖不再
101
+ 空白,別名登入頁有桌布、按鈕色與 App icon;可用 `login_style` 換主色、桌布與標題圖。
102
+
103
+ ### Changed
104
+
105
+ - **Incompatible:** the CLI needs SDK 0.10.3 or later. `check` and `build` refuse an App
106
+ whose backend requires SDK 0.10.0, 0.10.1 or 0.10.2 (`SDK_UNSUPPORTED`, naming 0.10.3);
107
+ raise the Go module, Cargo tag or `backend.sdk` and the frontend's
108
+ `@synopackageland/sdk` to 0.10.3. The CLI no longer carries the fallbacks for those
109
+ releases: the pre-`timeouts.json` timeouts, skipping `executor check-policy` for an SDK
110
+ without it, and `EXECUTOR_START_STEP_UNSUPPORTED`.
111
+
112
+ 繁中摘要:CLI 需要 SDK 0.10.3 以上;App 若仍用 SDK 0.10.0~0.10.2,`check`/`build` 會以
113
+ `SDK_UNSUPPORTED` 拒絕。移除為舊版保留的逾時退回值、check-policy 略過與 start 步驟檢查。
114
+
115
+ - `check` and `build` refuse a request deadline that ends before a root program `root.run`
116
+ may run would answer (`STANDALONE_REQUEST_TIMEOUT_SHORT`): `backend.timeout_seconds`
117
+ (60 by default) must be at least the program's `run.timeout_s` (20 by default) + 20, the
118
+ executor's 10 seconds and its client's 10 (SDK `spec/protocol/v1`, Timeouts). The default
119
+ covers programs of up to 40 seconds; a 120-second program needs `timeout_seconds: 140`.
120
+ A program with only `job`, `start` or `stop` is not affected.
121
+ - The request default, the window's wait past the request deadline and the `root.run`
122
+ deadline check read their values from the App's SDK, `spec/protocol/v1/timeouts.json`,
123
+ instead of the CLI's own copies, so they follow the SDK the App builds with. An App
124
+ without the SDK keeps 60 seconds, 2 seconds, and a `run.timeout_s` of 20 + 20. The
125
+ generated values do not change with SDK 0.10.3.
126
+
127
+ 繁中摘要:request 預設逾時、視窗多等的時間與 root.run 逾時檢查改讀 App 所用 SDK 的
128
+ timeouts.json,不再用 CLI 手抄的值;沒有 SDK 的 App 沿用 60 秒、2 秒、20 + 20。
129
+ - The DSM window's approval and `control start` wait for an executor that answers
130
+ `state: "starting"` while it runs its start step, for up to 75 seconds, instead of giving
131
+ up after 30 seconds; past that the result is blocked with code `executor_starting`, and the
132
+ plain-UI panel and the SDK settings say the service is still starting. The plain-UI panel
133
+ says it is waiting for the background service while a request runs.
134
+
135
+ 繁中摘要:核准與 control start 會等待回報「starting」的 executor(最多 75 秒),超過則以
136
+ `executor_starting` 阻擋並說明仍在啟動。
137
+
138
+ - The DSM window's approval asks DSM's schedulers 8 times instead of 15, following the SDK's
139
+ `spec/scheduler/v1`. The boot task is read with one EventScheduler `get` by its name
140
+ (DSM keeps event task names unique) and never listed; Task Scheduler is listed once before
141
+ and once after the writes, and the control task is then read by its listed id. A step reads
142
+ each task once and reuses that read; a delete still reads the task again first. After the
143
+ boot task runs, only `executor.status` is asked: the tasks were read back exactly just
144
+ before, and the executor's pins answer for the rest. `executor.status` is asked every
145
+ 500 ms for the first 4 seconds, then backs off to 2 seconds. `status` (and `check`) gets the
146
+ boot task by name instead of listing EventScheduler. Measured on a DSM 7.4 x86_64 NAS with
147
+ a fresh install: ready came 1.1-4.9 s after the boot task ran instead of 6.3-10.9 s, and
148
+ 10.1-21.8 s after Approve instead of 16.3-26.9 s (DSM's scheduler calls vary by seconds).
149
+
150
+ 繁中摘要:視窗核准對 DSM 排程 API 的呼叫從 15 次降為 8 次:boot 任務改用名稱 get、不再 list
151
+ EventScheduler;Task Scheduler 在寫入前後各 list 一次,之後用 id get;執行 boot 任務後只問
152
+ `executor.status`,前 4 秒每 500 ms 問一次。安全檢查(重複、內容逐字比對、pins)都保留。
153
+
154
+ ### Fixed
155
+
156
+ - The DSM window's `openFileManager` and `revealFile` take a `/volumeN/...` path as the SDK's
157
+ `fileStationPath` does: a DSM volume mount (`/volumeN`, `/volumeUSBN`, `/volumeSATAN`) is
158
+ stripped instead of refused, and the reveal recovery follows the path File Station got.
159
+ Anything but one canonical shared-folder path is still `invalid-input`.
160
+ - Executor approval results carry a structured `code` beside the English `reason`, and an
161
+ `outcome` when the approval rolled back: a refused administrator password is
162
+ `password_rejected` (DSM 3121), other PasswordConfirm refusals `password_required`,
163
+ `session_ended` or `password_confirm_failed`; scheduler, task and executor failures have
164
+ codes of their own (`scheduler_denied`, `task_missing`, `executor_unreachable`, …) and the
165
+ outcomes are `restored`, `withdrawn`, `restore_failed` and `withdraw_failed`. The SDK's
166
+ `ExecutorSettings` reads `code`. The plain-UI window panel now shows the code and outcome in
167
+ the DSM language, followed by the DSM code (`dsm_3121`), instead of the English reason.
168
+ Rebuild the App to pick this up.
169
+
170
+ 繁中摘要:核准結果新增結構化 `code`(例如 `password_rejected`)與回復結果 `outcome`,`reason`
171
+ 仍保留英文細節;純 UI 視窗面板改以 DSM 語系顯示原因,不再直接顯示英文;需重新 build App。
172
+
173
+ - Executor approval: a scheduler write that fails partway no longer leaves the earlier
174
+ approval half deleted. The approval stops at the first failed write, withdraws the tasks
175
+ it created and recreates the earlier boot and control tasks it removed, then offers
176
+ approval again. DSM cannot hold two same-named tasks, so the old task is still deleted
177
+ before its replacement is created. When the rollback itself fails, the window says so, and
178
+ the next approval converges from what is left. A DSM session that ended (`dsm_106`,
179
+ `dsm_107`, `dsm_119`) asks the administrator to sign in to DSM again.
180
+
181
+ 繁中摘要:核准半途寫入失敗時,會撤回這次建立的排程並重建先前核准的舊排程,再提供重新核准;
182
+ 還原失敗時會明白說明,下次核准可自動收斂。DSM session 失效時提示重新登入。
183
+
184
+ - An App opened through its Application Portal alias (`https://nas/<alias>/`) reaches its
185
+ backend again. The generated window posted `ctx.call` and `ctx.files` to the absolute
186
+ `/webman/3rdparty/<package>/cgi/api.cgi`, which DSM answers with 403 on the alias, so every
187
+ call failed with `bad_response` and an approved executor read as unavailable. The path is
188
+ now relative to the page, as the App's own assets are; the DSM desktop resolves it to the
189
+ same URL as before. Rebuild the App to pick this up.
190
+
191
+ 繁中摘要:從應用程式入口別名網址開啟 App 時,`ctx.call`/`ctx.files` 改用相對路徑,不再被
192
+ DSM 以 403 拒絕(原本顯示 `bad_response`、已核准的 executor 顯示無法使用);需重新 build App。
193
+
194
+ - `check` and `build` refusing a `root.start`, `root.stop` or `uninstall.purge` program that
195
+ also declares `run` or `job` (`EXECUTOR_POLICY_REJECTED`) now say what to do: give the step
196
+ another program name in `root.programs`, one without `run` or `job`.
197
+
198
+ 繁中摘要:start/stop/purge 的 program 同時宣告 run/job 而被拒時,提示改用另一個沒有
199
+ run/job 的 program 名稱。
200
+
201
+ - `init` and `build` of a Python App whose SDK has no Python SDK (`python/synopkgland_app`)
202
+ report `STANDALONE_PYTHON_SDK_MISSING` with the path, instead of a bare `ENOENT`.
203
+
204
+ 繁中摘要:找不到 Python SDK 時改回專屬錯誤碼 `STANDALONE_PYTHON_SDK_MISSING`,不再是 `ENOENT`。
205
+
206
+ ### Removed
207
+
208
+ - `@synopackageland/cli/authorization-sdk` is gone: `ROOT_RUNTIME` and
209
+ `AUTHORIZATION_RUNTIME`, the window runtimes of a native product with its own pinned root
210
+ tasks, ship unchanged in `@synopackageland/sdk/native`. A native product imports them from
211
+ the SDK; generated Apps are not affected.
212
+
213
+ 繁中摘要:native 產品的 root/authorization 視窗 runtime 改由 SDK(`@synopackageland/sdk/native`)提供,
214
+ CLI 不再匯出 `authorization-sdk`;一般產生的 App 不受影響。
215
+
216
+ - The CLI's copy of the Application Portal panel text (19 keys in 9 languages) is gone:
217
+ `build` reads it from the App's SDK (`ts/i18n/portal/<locale>.json`, in every SDK 0.10.x),
218
+ the same text the SDK's `PortalSettings` shows. The built window is unchanged; a fix to the
219
+ text reaches an App with its next SDK.
220
+
221
+ 繁中摘要:Application Portal 面板文案改從 App 的 SDK 讀取,CLI 不再保留一份;建出的視窗不變,文案修正隨 SDK 更新。
222
+
223
+ - `dist/runtime/delegate-contract.js` keeps only the operation names the build checks; the
224
+ background-save DTOs, route table and limits, which nothing in the CLI used, and the frozen
225
+ v1 vector now live only in the SDK (`ts/src/delegate/long-running.ts`).
453
226
 
454
- ### SDK 0.9.1
455
227
 
456
- - Depends on `@synopackageland/templates` 0.9.1: `init` scaffolds Apps that
457
- require SDK 0.9.1 (`backend/go.mod` and `@synopackageland/sdk`).
458
- - The CLI records the SDK 0.9.1 executor digests, rebuilt with the
459
- uninstall cleanup (x86_64 `5d75af3c…`, aarch64 `b0e1f817…`, armv7
460
- `17db613a…`; lifecycle digests equal 0.9.0). SDK 0.9.1 only replaces the
461
- executor: the policy format, task scripts and permissions are unchanged,
462
- so the policy digest stays the same and no App needs a new approval. An App moved from SDK
463
- 0.9.0 to 0.9.1 in `backend/go.mod` keeps its approval; the installed
464
- executor re-pins the new executor by itself.
465
-
466
- ### Cleaning up after 0.9.0 installs
467
-
468
- An App built with SDK 0.9.0 could leave root tasks behind at uninstall
469
- (t-0294 bugs 1 and 3). If the App is still installed, rebuild it with SDK
470
- 0.9.1, install that build and then uninstall it: the 0.9.1 purge rebuilds the
471
- task inventory from the approved policy and deletes the tasks. Otherwise an
472
- administrator removes them by hand:
473
-
474
- - Control Panel > Task Scheduler: delete `<package> executor control` and
475
- `<package> executor selection`.
476
- - The boot event, which DSM's web API refuses to delete over HTTP (error
477
- 119): as root, run
478
- `/usr/syno/bin/synowebapi --exec api=SYNO.Core.EventScheduler method=delete version=1 task_name="<package> executor boot"`.
479
- - Control Panel > Application Privileges: remove the App's rules. Control
480
- Panel > Login Portal: if its alias still opens, save an application alias
481
- there to reload nginx, or restart the NAS. The 0.9.1 rebuild-and-uninstall
482
- route above does both for you.
483
-
484
- ### Approval after upgrading (correction to 0.9.0)
485
-
486
- - 0.9.0 said a delegate App rebuilt with it needs one new approval. Measured
487
- on DSM 7.4 (t-0294): a `files.moveFromStaging` App upgraded from 0.8.0 to
488
- 0.9.0 kept the same policy digest. The installed executor re-pinned the new
489
- executor by itself, with no new approval. Only an `install-selected` scope
490
- needed one administrator confirmation (PasswordConfirm) of the shared
491
- folder.
492
- - A new approval is needed only when the executor policy changes, for
493
- example new operations, another scope or changed root scripts.
494
-
495
- ### Release tooling
496
-
497
- - `node scripts/executor-digests.mjs <sdk>/release/vX.Y.Z.json` records a
498
- released SDK's executor and lifecycle digests in
499
- `src/runtime/executor-digests.json`. It checks the three architectures,
500
- go1.26.9 and the CLI minor. A tagged release (`commit` set) never changes
501
- a recorded version; a release not yet tagged (`commit: null`) replaces its
502
- own entry. SDK 0.9.1's digests were recorded this way.
503
- - `test/fixtures/executor-setup/` holds the `executor/setup.json` this CLI
504
- writes for delegate, time-limited delegate with jobs, root-ops and
505
- install-selected Apps, for the SDK executor's contract test.
506
- `UPDATE_EXECUTOR_SETUP_FIXTURES=1 npm test` regenerates them.
507
- - The npm gate runs `scripts/smoke-exports.mjs`, the export check of
508
- `smoke-pack.sh`. In 0.9.0 that check still imported the removed
509
- `authorizationTemplateDirectory`, so `gate.sh docker` always failed.
510
-
511
- ## 0.9.0 (2026-10-11)
512
-
513
- The CLI, the SDK and the project templates are now three repositories, and
514
- the CLI only packages, deploys and checks. An App names its SDK version once,
515
- in `backend/go.mod`. Background saves can create subfolders and choose what
516
- happens on a name conflict, and time-limited delegate jobs gain read, rename,
517
- copy, move and delete.
518
-
519
- 繁中摘要:CLI 移到 github.com/synopackageland/syno-pkg-land-cli,只負責打包、部署與檢查;
520
- 範本改由 `@synopackageland/templates`(精確 pin)提供;SDK 是
521
- `github.com/synopackageland/syno-pkg-land-sdk`(Go)與 `@synopackageland/sdk`(前端,含
522
- `./vue`),版本只寫在 `backend/go.mod`。`build` 依該版本 `go mod download`
523
- executor 並比對 CLI 內建的 sha256 表。`check` 新增 `SDK_REQUIRED`、
524
- `SDK_VERSION_MISMATCH`、`SDK_UNSUPPORTED`。`files.moveFromStaging` 的 `name`
525
- 可帶子資料夾,新增 `conflict: fail|overwrite|rename`;有時限的 delegate 新增
526
- `files.read`、`files.rename`、`files.copy`、`files.move`、`files.delete`。
527
- 管理員只在 executor policy 改變時需要重新核准;0.9.0 的 policy 有改變。
228
+ ## 0.10.2 (2026-10-14)
528
229
 
529
- ### Repositories and the SDK version
230
+ First release of the changelog. `@synopackageland/cli` (`synopkgland`)
231
+ builds Synology DSM 7 packages (SPK) from a template, checks them, and
232
+ installs them on a NAS. It builds with SDK 0.10.2 and templates 0.10.2.
530
233
 
531
- - The CLI is github.com/synopackageland/syno-pkg-land-cli, published as
532
- `@synopackageland/cli`. The SDK is github.com/synopackageland/syno-pkg-land-sdk: the Go
533
- module `github.com/synopackageland/syno-pkg-land-sdk` and the npm package
534
- `@synopackageland/sdk`, which includes the Vue components at
535
- `@synopackageland/sdk/vue`.
536
- - The `require github.com/synopackageland/syno-pkg-land-sdk` line in `backend/go.mod` is the
537
- only SDK version. `dsm-package.yaml` has no SDK field.
538
- - `check` and `build` report `SDK_REQUIRED` when `capabilities.delegate` or
539
- `rootOps` is declared without that line (an `entry` mode without it fails
540
- with `STANDALONE_ENTRY_UNSUPPORTED`), `SDK_UNSUPPORTED` when its minor version is not the CLI's (CLI 0.9.x builds
541
- SDK 0.9.x), and `SDK_VERSION_MISMATCH` when the frontend's
542
- `@synopackageland/sdk` differs from it. A `file:` frontend dependency is
543
- compared through that folder's `package.json` version.
544
- - `build` reads the SDK version from `backend/go.mod`, runs `go mod download`
545
- for the executor of that version, and builds it with go1.26.9. Its sha256
546
- must match the table shipped in this CLI, or the build fails with
547
- `EXECUTOR_DIGEST_MISMATCH`. A version Go cannot fetch fails with
548
- `SDK_DOWNLOAD_FAILED`. While the SDK repositories are private, set
549
- `GOPRIVATE=github.com/synopackageland/*` and give git a credential for
550
- github.com; the error says which of the two is missing. Root task scripts
551
- and `sdk-permissions.json` come from the same SDK version.
552
- - The CGI binds both policy digests at link time:
553
- `<module>/internal/api.expectedPermissionsDigest` for the App and
554
- `github.com/synopackageland/syno-pkg-land-sdk/api.expectedSDKPermissionsDigest` for the
555
- SDK.
556
- - `@synopackageland/cli/authorization-sdk` exports `AUTHORIZATION_RUNTIME`
557
- and `ROOT_RUNTIME`. `authorizationTemplateDirectory` is removed;
558
- `AuthorizationGate` is in `@synopackageland/sdk/vue`.
234
+ 繁中摘要:`synopkgland` 從模板建立 DSM 7 套件(SPK),檢查、打包並安裝到
235
+ NAS;搭配 SDK 0.10.2 與 templates 0.10.2。
559
236
 
560
237
  ### Templates
561
238
 
562
- - `init` and `add` read the templates from `@synopackageland/templates`, a
563
- dependency pinned to an exact version. `SYNOPKGLAND_TEMPLATES_DIR` points
564
- them at a templates checkout; without either, `TEMPLATES_MISSING`.
565
- - `init` fills the placeholders in every generated file; the Go module path
566
- of a new backend is the package id. The next steps say
567
- `(cd frontend && npm install)`: the templates ship no lockfile.
568
- - `init` and `add` share one capability overlay table (`root-ops`, `jobs`,
569
- `delegate`).
570
-
571
- ### Local SDK checkout
572
-
573
- - With `SYNOPKGLAND_SDK_DIR`, `init` adds
574
- `replace github.com/synopackageland/syno-pkg-land-sdk => $SYNOPKGLAND_SDK_DIR` to
575
- `backend/go.mod` and sets the frontend dependency to
576
- `file:$SYNOPKGLAND_SDK_DIR/ts`; install it with
577
- `(cd frontend && npm install --install-links)`, which packs the SDK like the
578
- released package. npm does not build a `file:` package, so a Vue project
579
- needs `(cd $SYNOPKGLAND_SDK_DIR/ts && npm ci && npm run build)` first, and
580
- again after SDK changes; `init` refuses a checkout without `ts/dist`, or
581
- without a root `go.mod`, with `SDK_DIR_NOT_READY`.
582
- - `build` uses `$SYNOPKGLAND_SDK_DIR/executor`, skips the digest table and
583
- warns with `EXECUTOR_DIGEST_UNVERIFIED`. Do not
584
- publish such an SPK; the release script refuses to run while
585
- `SYNOPKGLAND_SDK_DIR` or `SYNOPKGLAND_TEMPLATES_DIR` is set.
586
-
587
- ### Delegate: subfolders and conflict policy
588
-
589
- - `files.moveFromStaging` (Go: `delegate.MoveParams.Name`) accepts folders
590
- below the destination, for example `Album/CD1/01.flac`. The executor's move
591
- child creates missing folders as the user, after the final revoke check, so
592
- a canceled move leaves none behind. Existing folders are reused. Limits: 16
593
- folders, 255 bytes per name, 1024 bytes in total. A name without folders
594
- behaves exactly as before.
595
- - New `conflict` field for `files.moveFromStaging` and the time-limited
596
- `files.write`: `fail` (default, unchanged), `overwrite` (atomic replace of a
597
- regular file the user can write) or `rename` (`name (1).ext` up to
598
- `name (99).ext`; the receipt path reports the name used).
599
- - New error codes: `invalid-path`, `path-too-deep`,
600
- `destination-not-directory`, `name-too-long`.
601
-
602
- ### Delegate: file operations for time-limited grants
603
-
604
- - New operations `files.read`, `files.rename`, `files.copy`, `files.move` and
605
- `files.delete`, each declared on its own; a write operation never implies
606
- delete. Go: `job.Files()` returns `delegate.JobFiles` with `Read`, `Rename`,
607
- `CreateFolderAll`, `Copy`, `Move`, `Delete`, `TaskStatus` and `CancelTask`.
608
- - `files.createFolder` takes `parents` (`CreateFolderAll`, like `mkdir -p`).
609
- - Except `files.write` and `files.read`, the executor calls File Station as the
610
- user through one root-side allowlist of methods, and re-checks the paths File
611
- Station reports against the grant. `files.read` copies the file into staging
612
- in a child that drops to the user and never follows symlinks.
613
- - Copy, move and delete run as File Station background tasks followed with the
614
- implied `files.taskStatus` and `files.taskCancel`; revoking the grant stops
615
- them. Delete needs `recursive` for folders and goes to the recycle bin; a
616
- Shared Folder without one needs `permanent`.
617
- - File Station refusals now report SDK codes (`PERMISSION_DENIED`,
618
- `not-found`, `destination-exists`, `NO_SPACE`, `invalid-path`,
619
- `name-too-long`) instead of `DSM_FAILED`. New codes: `not-a-file`,
620
- `recursive-required`, `recycle-bin-disabled`, `task-not-found`,
621
- `too-many-tasks`, `READ_FAILED`.
622
-
623
- ### Approval
624
-
625
- - An administrator approves the executor policy (operations, scope and root
626
- tasks), not the executor binary. When an upgrade keeps the policy and the
627
- new executor matches the `INFO` anchor, it installs without a new approval.
628
- When the policy changes, the App stays at `needs-admin-approval` until an
629
- administrator approves again.
630
- - The 0.9.0 policy changes (new operations and `conflict`), so a delegate App
631
- rebuilt with this CLI needs one new approval after the upgrade.
632
- Moving the SDK to its own repository does not add one. Apps not rebuilt are
633
- not affected.
634
-
635
- ## 0.8.0 (2026-10-11)
636
-
637
- Faster builds, one SPK checker for any package, and a guide for scripts and
638
- interpreted languages. `validate` is merged into `inspect`.
639
-
640
- 繁中摘要:toolkit 改用使用者層級共用快取;新增 `build --jobs` 多架構並行與
641
- `build --verify-reproducible` 可重現檢查(可用依賴指紋略過);`inspect` 對任何
642
- SPK 檢查 DSM 7 格式,synopkgland 建置的 SPK 另查建置合約;native layout 的
643
- aarch64 INFO arch 改寫機型清單、拒絕無執行權限的 script、toolkit 可用 GNU tar
644
- 或 bsdtar 解開;新增腳本/直譯語言打包指南。無圖示的 native layout SPK 現在會
645
- 因 `SPK_ICON_MISSING` 無法通過 inspect。
646
-
647
- ### Shared toolkit cache
648
-
649
- - Native layout toolkit archives now default to one per-user cache shared by
650
- every project and worktree: `$XDG_CACHE_HOME/synopkgland` or
651
- `~/.cache/synopkgland` on Linux, `~/Library/Caches/synopkgland` on macOS.
652
- `SYNOPKGLAND_CACHE_DIR` still overrides it. The project's
653
- `.synopkgland-cache` folder is no longer used by default; delete it or point
654
- `SYNOPKGLAND_CACHE_DIR` at it. The private-folder and `sha256` checks are
655
- unchanged, and concurrent builds may share the cache.
656
-
657
- ### Parallel architecture builds
658
-
659
- - `synopkgland build --jobs <n>` builds up to `n` architectures at the same
660
- time for native-go, binary, native layout and Compose gateway projects. The
661
- default stays `1`. Output files and names are byte-identical to a sequential
662
- build; with `n` above 1 each output line is prefixed with its architecture.
663
- - SPK files now reach the output folder only after every architecture
664
- succeeds, also with `--jobs 1`. A failed architecture is named in the error,
665
- for example `[aarch64]`, and no SPK from that build is written. Before, the
666
- architectures built before the failure stayed in `dist/`.
667
- - Native layout compile hooks get `SYNOPKGLAND_WORK_DIR`, an empty scratch
668
- folder per architecture. `--jobs` above 1 requires `layout.payload` to
669
- contain `{arch}`. Architectures that pin the same toolkit archive download it
670
- once.
671
- - New error code `CLI_JOBS_INVALID`.
672
-
673
- ### Reproducible build check
674
-
675
- - `synopkgland build --verify-reproducible [changed|always]` keeps this
676
- build's SPKs as the first round, builds once more into a temporary folder
677
- with an empty `GOCACHE` after removing the manifest's `reproducible.clean`
678
- paths, and fails with `BUILD_NOT_REPRODUCIBLE` listing the payload and
679
- metadata files that differ. Works for every project type.
680
- - New optional manifest block `reproducible: {inputs, clean}` in
681
- `dsm-package.yaml` (native-go, binary, native layout) and
682
- `synology-app.yaml`. The SHA-256 of the `inputs` files, the CLI version and
683
- native layout toolkit pins form a dependency fingerprint. A passing check
684
- writes it to `reproducible-fingerprint.json`; `changed` (the default) skips
685
- the second build while it is unchanged. New error code
686
- `REPRODUCIBLE_INVALID`.
687
-
688
- ### `inspect` checks any SPK
689
-
690
- - `synopkgland inspect <spk>` now checks any SPK against the DSM 7 package
691
- format, whether `synopkgland` built it or not, without installing it: the
692
- outer archive, `INFO` fields, formats and `checksum`, path and link safety in
693
- `package.tgz`, `ui/config` JSON and its `"texts"` declaration, ELF files
694
- against `arch` (none in `noarch`), lifecycle scripts and their execute bit,
695
- `conf/privilege` and `conf/resource`, icons, `LICENSE`, wizards and
696
- `syno_signature.asc`. Every finding has a stable `SPK_*` code listed in the
697
- errors reference.
698
- - Only an SPK that `synopkgland` built (top-level `sdk-package.json` or
699
- `native-layout.json`, or `synology-app.yaml` in the payload) is also checked
700
- against the build contract. Other SPKs are no longer rejected by it.
701
- - The output has a DSM format section and, for `synopkgland` SPKs, a
702
- `synopkgland` contract section. An error in either exits 1 with
703
- `SPK_INSPECTION_FAILED`; warnings alone exit 0. `--json` keeps every earlier
704
- field and adds `synopkgland` (boolean) and `dsm: {ok, errors, warnings}`.
705
- - There is no `validate` command; the DSM rules it had during 0.8.0
706
- development are part of `inspect`. The project check is `check`.
707
-
708
- ### Native layout fixes
709
-
710
- - `arches: [aarch64]` now writes the same `INFO` `arch` as native-go and
711
- binary (`armada37xx rtd1296 rtd1619 rtd1619b`) instead of `aarch64`, which
712
- the Developer Guide arch table does not list.
713
- - `build` refuses a lifecycle or replace script in `layout.metadata` without
714
- the execute bit with `NATIVE_LAYOUT_SCRIPT_NOT_EXECUTABLE`; run `chmod +x`.
715
- Before, the SPK was built and DSM could not run the script.
716
- - `native.toolkits` archives extract with GNU tar and with bsdtar (the macOS
717
- default). Other tar programs fail with `NATIVE_TOOLKIT_TAR_UNSUPPORTED`.
718
- Members with absolute or `..` paths are refused.
719
-
720
- ### Scripts and interpreted languages guide
721
-
722
- - New guide: package a shell, Node.js, Python, Bun or Deno program by
723
- compiling it to one executable, bundling the interpreter through
724
- `native.toolkits`, or running it in a Compose container, with measured sizes,
725
- architectures and glibc needs, and a hand-packed `noarch` shell SPK. The CLI
726
- cannot declare `install_dep_packages` for Package Center's Node.js or Python
727
- packages; the guide says so.
728
-
729
- ### 繁體中文摘要
730
-
731
- - `synopkgland build --jobs <n>` 可同時建置多個架構(native-go、binary、native layout、
732
- Compose gateway),預設仍為 1;產出與依序建置逐位元組相同。所有架構成功後才寫入
733
- SPK,失敗時錯誤標明架構且不寫入任何 SPK。native layout hook 新增
734
- `SYNOPKGLAND_WORK_DIR`;`--jobs` 大於 1 時 `layout.payload` 必須含 `{arch}`。
735
- - `synopkgland build --verify-reproducible` 以這次建置為第一輪、從乾淨狀態再建一次比對,
736
- 不同時列出 payload 檔案並以 `BUILD_NOT_REPRODUCIBLE` 失敗。manifest 新增
737
- `reproducible: {inputs, clean}`;依賴指紋未變時略過(記錄在
738
- `reproducible-fingerprint.json`)。
739
- - native layout 的 toolkit 快取預設改為每位使用者一份、所有專案與 worktree 共用
740
- (Linux `$XDG_CACHE_HOME/synopkgland` 或 `~/.cache/synopkgland`,macOS
741
- `~/Library/Caches/synopkgland`);`SYNOPKGLAND_CACHE_DIR` 仍可覆寫。專案內的
742
- `.synopkgland-cache` 不再預設使用。
743
- - `synopkgland inspect <spk>` 改為不安裝、依 DSM 7 套件格式檢查任何 SPK(不限本 CLI 建置),
744
- 涵蓋外層封存、`INFO` 欄位與 `checksum`、`package.tgz` 路徑與連結安全、`ui/config` 的
745
- `"texts"`、ELF 與 `arch`(`noarch` 不得有 ELF)、生命週期指令碼與執行權限、`conf/privilege`
746
- 與 `conf/resource`、圖示、`LICENSE`、精靈與簽章。每個問題有固定 `SPK_*` 代碼,列在錯誤碼參考。
747
- - 只有 `synopkgland` 建置的 SPK(最上層有 `sdk-package.json` 或 `native-layout.json`,或 payload
748
- 有 `synology-app.yaml`)另查建置合約;其他 SPK 不再因合約被拒。任一區段有錯誤時以
749
- `SPK_INSPECTION_FAILED` 結束碼 1 失敗,只有警告時結束碼 0。`--json` 保留原有欄位,新增
750
- `synopkgland` 與 `dsm: {ok, errors, warnings}`。
751
- - 沒有 `validate` 指令;0.8.0 開發期間的 DSM 規則已併入 `inspect`。專案檢查請用 `check`。
752
- - native layout 以 `arches: [aarch64]` 建置時,INFO `arch` 改為與 native-go/binary 相同的
753
- `armada37xx rtd1296 rtd1619 rtd1619b`。`layout.metadata` 的指令碼沒有執行權限時 `build` 以
754
- `NATIVE_LAYOUT_SCRIPT_NOT_EXECUTABLE` 拒絕,請 `chmod +x`。`native.toolkits` 可用 GNU tar 與
755
- bsdtar 解壓,其他 tar 以 `NATIVE_TOOLKIT_TAR_UNSUPPORTED` 失敗。
756
- - 新增「封裝指令碼與直譯式語言」指南:編譯成單一執行檔、經 `native.toolkits` 附帶直譯器、或以
757
- Compose 容器執行,附實測大小、架構與 glibc 需求,以及手動打包的 `noarch` shell SPK。CLI 無法宣告
758
- `install_dep_packages` 依賴套件中心的 Node.js/Python 套件,指南已明確說明。
759
-
760
- ## 0.7.3 (2026-10-11)
761
-
762
- Rebuild every package built with 0.7.2 that has `i18n.info` translations. In
763
- 0.7.2 its DSM main-menu entry, desktop shortcut and window title showed the raw
764
- key `synopkgland:app_title` in every language.
765
-
766
- ### Translated DSM title fix
767
-
768
- - `ui/config` entries that use the `synopkgland:app_title` and `app_desc`
769
- strings now declare `"texts": "texts"`: the main-menu `app` entry, its window
770
- `lib` entry and the `.url` entry of a `synology-app.yaml` app. DSM loads
771
- `ui/texts/<DSM language>/strings` only for entries that declare `texts`, so
772
- 0.7.2 titles never resolved (seen on DSM 7.4 and 7.1.1). Without `i18n.info`
773
- `ui/config` is unchanged.
774
-
775
- ### Correction to 0.7.2
776
-
777
- - 0.7.2 said `synology-app.yaml` `i18n.info` localizes the DSM window title.
778
- It localizes the DSM main-menu and desktop label only. `launch: dsm-window`
779
- adds a main-menu and desktop entry that DSM always opens in a new browser
780
- tab, as it does for every URL entry; the app has no DSM window and no window
781
- title. This is not caused by DSM's CSRF protection setting. The container app
782
- guide and the `synology-app.yaml` reference now say so.
783
-
784
- ### 繁體中文摘要
785
-
786
- - 修正 0.7.2 的在地化標題:有 `i18n.info` 翻譯時,`ui/config` 中使用 `synopkgland:` 字串的
787
- `app`、`lib` 與 `.url` 項目都加上 `"texts": "texts"`。0.7.2 少了這個欄位,DSM 不會載入
788
- `ui/texts/<語言>/strings`,主選單、桌面捷徑與視窗標題在各語言都顯示 `synopkgland:app_title`。
789
- - 用 0.7.2 建置且含 `i18n.info` 的套件,請以 0.7.3 重新建置並遞增 build 後重新發佈;沒有
790
- `i18n.info` 的套件不受影響。
791
- - 更正 0.7.2:`synology-app.yaml` 的 `i18n.info` 只翻譯 DSM 主選單與桌面標籤,沒有視窗標題。
792
- `launch: dsm-window` 的項目一律由 DSM 在新的瀏覽器分頁開啟(DSM 對所有網址項目都如此),
793
- 與 CSRF 防護設定無關;容器 App 指南與 `synology-app.yaml` 參考已更正。
794
-
795
- ## 0.7.2 (2026-10-11)
796
-
797
- Rebuild packages to pick up the new default icon. Packages without `icon` now
798
- show the SDK default icon instead of a solid blue square.
799
-
800
- ### Package icon
801
-
802
- - `dsm-package.yaml` and `synology-app.yaml` accept `icon`: a square PNG of at
803
- least 256×256 inside the project, or a folder of `icon_<size>.png` (16, 24,
804
- 32, 48, 64, 72, 256; `icon_256.png` required, missing sizes scaled from it).
805
- `build` writes `PACKAGE_ICON.PNG` (64×64), `PACKAGE_ICON_256.PNG` and every
806
- DSM window size `ui/images/app_<size>.png` from it. SVG is not accepted, so
807
- the CLI gains no image dependency. `check` refuses an icon outside the
808
- project, a non-PNG, a non-square or smaller image, and one whose Package
809
- Center icons exceed the package source's 256 KiB limit
810
- (`STANDALONE_ICON_INVALID`, `CONTRACT_ICON_INVALID`).
811
-
812
- - A native layout may set `icon` too; `build` then writes both Package Center
813
- icons and refuses a metadata folder that also ships them. Without `icon` the
814
- layout keeps owning its icons.
815
-
816
- - `check` reports which icon a build uses: `圖示:自訂 <path>`, or `圖示:SDK
817
- 預設圖示` with an info hint about `icon:` (not an error). It warns when an
818
- icon is a single colour. `check --json` reports it in `data.icon`.
819
-
820
- - `inspect` reports `icon` as `default`, `custom` (with the SHA-256 of
821
- `PACKAGE_ICON_256.PNG`) or `none`, and lists a Package Center icon over
822
- 256 KiB, of the wrong size or not a PNG as a finding.
823
-
824
- - The SDK default icon replaces the solid blue square in Package Center, the
825
- DSM main menu and the window. Sources are in `assets/default-icon/`.
826
-
827
- ### Translated DSM title
828
-
829
- - With `i18n.info` translations, the DSM main-menu entry and window title
830
- follow the DSM language. `ui/config` refers to `synopkgland:app_title` and
831
- `app_desc` in `ui/texts/<DSM language>/strings`, written for English and each
832
- translated language; a language without its own text shows the English one.
833
- Without translations `ui/config` keeps the literal English title.
834
-
835
- - `synology-app.yaml` accepts the same `i18n.info` block. It writes INFO
836
- `displayname_<lang>` / `description_<lang>` and, with `launch: dsm-window`,
837
- localizes the DSM window entry. Once a translation exists, the English
838
- `displayname` and `description` must also be one line without `"` or `\`
839
- (`CONTRACT_I18N_INVALID`).
840
-
841
- ### Delegate window
842
-
843
- - A DSM window built for a `scope: install-selected` delegate no longer
844
- contains the dead `if (panels && false)` check. The window leaves out the
845
- approval panel code instead, and other scopes generate `if (panels)`. The
846
- behavior is unchanged: this scope is approved only in the app's
847
- **Administrator setup** panel, because that approval carries the scope the
848
- administrator picks and the window panel has no scope picker. The delegate
849
- guide now says so.
850
-
851
- ### 繁體中文摘要
852
-
853
- - 升級後重新建置套件即可換上新的 SDK 預設圖示(不再是純藍方塊)。executor digest 不變,不需重新核准。
854
- - `dsm-package.yaml`、`synology-app.yaml` 與 native layout 新增 `icon`:專案內至少 256×256 的正方形
855
- PNG,或含 `icon_256.png` 的 `icon_<size>.png` 目錄;不接受 SVG。`check` 顯示使用的圖示並檢查格式與
856
- 256 KiB 上限,`inspect` 回報 `default`/`custom`/`none`。
857
- - 有 `i18n.info` 翻譯時,DSM 主選單與視窗標題會跟著 DSM 語言切換;缺翻譯的語言顯示英文。
858
- `synology-app.yaml` 也支援 `i18n.info`。
859
- - `scope: install-selected` 的 DSM 視窗不再產生 `if (panels && false)` 死碼;此 scope 仍只在 App 的
860
- 「管理員設定」核准。
861
-
862
- ## 0.7.1 (2026-10-10)
863
-
864
- Rebuild packages to pick up template, hook and executor changes. The executor
865
- digest changed. A package built with CLI 0.7.0 needs one administrator approval
866
- after upgrading to a 0.7.1 build: the 0.7.0 executor opened the package
867
- `target` link without following it, so it cannot install the anchored executor
868
- itself. Executor-only upgrades between 0.7.1 or later builds need no approval.
869
-
870
- ### Executor upgrade and daemon timeouts
871
-
872
- - During an INFO-anchored executor-only upgrade the executor log said
873
- `executor paused: package pins require administrator approval` about two
874
- seconds before the new executor was ready. That line now appears only when
875
- the pins differ; an anchored handover logs only `executor upgrade:
876
- INFO-anchored executor installed under the approved policy`. The executor
877
- digest changes.
878
-
879
- - `executor_status_mismatch` in the app window and the app's settings names
880
- its cause: `the approved executor is not answering (<reason>)`, `the running
881
- executor reports other digests than this package pins`, or `the executor
882
- status request failed` with the request's own code, instead of `scheduler
883
- readback or operation failed` for all three. The Data and removal
884
- troubleshooting table explains each.
885
-
886
- - After approval or control start the window asked `executor.status` every
887
- 500 ms for up to 10 s. The wait now doubles from 500 ms to 2 s within the
888
- same 10 s budget: at most 7 requests instead of 20.
889
-
890
- - For live upgrade tests, the repository's `scripts/test/executor-bump.py`
891
- makes an executor-only build+1 SPK and now rewrites the window script's
892
- embedded pins with the INFO anchor and `executor/setup.json`. The earlier
893
- out-of-tree copy left the window pins behind, so the panel reported
894
- `executor_status_mismatch` after every such upgrade while CGI and CLI
895
- status were ready.
896
-
897
- - On kernel 3.10 (DSM 7.1, for example DS416 armv7) an executor-only
898
- upgrade, and approving again while the executor runs, no longer fail
899
- with "pid executable mismatch". That kernel names a running executor
900
- replaced by a rename after the rename source (`runtime/.executor
901
- (deleted)`), and root control accepted only `runtime/executor`. Root
902
- control now identifies the monitor by inode: the current
903
- `runtime/executor`, or an unlinked root-owned inode on the runtime's
904
- device whose last name was `executor` or `.executor*` in the runtime.
905
- The fix runs in the new executor, which the installed one starts: an
906
- upgrade from a package built with the target-link fix below needs no
907
- approval; from 0.7.0 it still needs the one approval described there.
908
- The executor digest changes.
909
-
910
- - When the approved tasks are ready but the executor does not answer
911
- (`executor_status_mismatch`), the app window offers Approve background
912
- access again. With the tasks approved, it restarts the executor without
913
- a password.
914
-
915
- - `executor.status` (root-ops) names approval, not "Start the package", as
916
- the next step when the package's last lifecycle request was start.
917
- Regenerate or copy `backend/internal/app/executor.go`.
918
-
919
- - An executor-only upgrade (same policy, new executor matching the INFO
920
- `synopkgland_executor_sha256` anchor) no longer fails with "executor
921
- upgrade refused: INFO-anchored executor could not be installed". The
922
- installer opened `/var/packages/<pkg>/target/executor` without following
923
- the DSM `target` link, which the executor refuses, so every such upgrade
924
- stayed paused. The fix runs in the executor that is already installed:
925
- an upgrade from a package built with an earlier SDK still needs one
926
- administrator approval. The executor digest changes.
927
-
928
- - `executor.status` (root-ops and delegate projects) no longer answers
929
- `internal` while the executor is paused (package stopped, or pins waiting
930
- for approval). It answers `{ready: false, state: "paused", reason:
931
- "executor_not_listening", next}`, and `synopkgland status` shows the
932
- reason and next step. Regenerate or copy
933
- `backend/internal/app/executor.go` into existing projects.
934
-
935
- - `daemon.timeouts` defaults are now `{start: 30, stop: 30, status: 5,
936
- uninstall: 60}` (were `start: 5, stop: 10, status: 2`). On vt-nas under
937
- IO load a boot start took more than 5 s and a Connect revoke more than
938
- 9 s. The new `timeouts.uninstall` (2–120) bounds `uninstall_args`
939
- separately; it used to get `stop - 1`, so an app had to raise the SIGKILL
940
- wait to give its revoke time. DSM's package unit allows 3600 s for start
941
- and stop. `inspect` still accepts SPKs built before `timeouts.uninstall`.
942
- The manifest reference explains how to pick values from a loaded NAS.
943
-
944
- ### Approval, identity and uninstall fixes
945
-
946
- - `identity: standalone`: "Continue with DSM" and every live revalidation
947
- failed with `session_required` after a DSM web login, because DSM web
948
- sessions enable SynoToken and DSM answers the `id` cookie alone with 119.
949
- The backend now forwards the request's single `X-SYNO-TOKEN` header, which
950
- the DSM window's `ctx.call` already sends, when it checks the DSM session.
951
- Sessions without SynoToken still verify with the cookie alone. The token is
952
- never put in a URL, logged or stored. This also unblocks root-ops approval
953
- in an `identity: standalone` package, which stopped at `executor.control`.
954
- Existing projects need the updated `internal/identity` files.
955
- - When approval created and read back both tasks but the executor then did
956
- not start or report healthy, the window left the tasks behind, and with no
957
- running executor there was no task inventory for uninstall to delete them.
958
- The window now withdraws the tasks that approval created; tasks from an
959
- earlier approval stay.
960
- - After a wrong password or a withdrawn approval, the window panel showed
961
- only "Check status". It and the app's settings now keep the approve action.
962
- - Uninstall no longer logs `task cleanup unconfirmed` for every successful
963
- delete: EventScheduler `delete` v1 and TaskScheduler `delete` v2 answer
964
- `{"success":true}` without data, which now counts as confirmed. The task
965
- definitions are unchanged, so no new approval is needed; the executor
966
- digest changes.
967
- - The native root window (`@synopackageland/cli/authorization-sdk`) gave up
968
- after about 30 s with `root_timeout`, while a loaded NAS ran the bootstrap
969
- task for up to 4.5 minutes. It now keeps waiting while the task is queued or
970
- running, up to 10 minutes, reports `{op, state, elapsedMs}` through
971
- `options.progress`, and the authorization flow accepts the backend's ready
972
- grant after `root_timeout` or `root_task_failed`.
973
- - A DSM certificate that Node does not trust failed with only
974
- `fetch failed`. The CLI now reports `DSM_TLS_UNTRUSTED` with the origin and
975
- certificate error and points to `NODE_EXTRA_CA_CERTS`; certificate checks
976
- stay on.
977
- - Docs: on DSM 7.1 the purge timing lines are only in the journal
978
- (`journalctl -t synopkgland-executor`); `init -t native-go` does not include
979
- `armv7` in `arches`, so add it for an armv7 NAS.
980
-
981
- ### Deploy and status fixes
982
-
983
- - `status` no longer reports `blocked` for every `identity: standalone`
984
- project. It opens a short-lived App session from the administrator's DSM
985
- session, reads `executor.status`, then logs out. That needs an `https://`
986
- host; over `http://` the capability stays `blocked` and the reason says so.
987
- - `deploy` accepts the install wizard values of an `install-selected`
988
- delegate: `--param delegate_share=<Shared Folder>` or
989
- `--param delegate_home=true`. A first install without one now fails with
990
- `DEPLOY_PARAM_REQUIRED` and a usage line before anything is built, instead
991
- of failing inside DSM. Invalid values fail with `DEPLOY_PARAM_TYPE_INVALID`.
992
- - `deploy` and `status` work on a native layout (existing native product).
993
- `deploy` builds the NAS architecture only; an `aarch64` NAS uses a declared
994
- `armv8`.
995
- - The deploy note no longer says every upgrade needs approval: an
996
- executor-only upgrade stays ready; a policy digest change needs approval.
997
- - Uninstall logs per-phase purge timing with the package name to the system
998
- log (`synopkgland-executor`): `task cleanup timing` per task and
999
- `purge timing` for the task and file phases. The executor digest changes.
1000
- - `add root-ops` and `add portal` print a notice on stderr when they raise
1001
- `os_min_ver`.
1002
- - `inspect` on an executor SPK describes the root boot and control tasks
1003
- instead of saying the package needs no root privileges.
1004
-
1005
- ### DSM 7.1: root-ops and delegate approval work
1006
-
1007
- - DSM 7.1.1 has no `SYNO.Core.TaskScheduler.Root` or
1008
- `SYNO.Core.EventScheduler.Root`, TaskScheduler stops at v3 and
1009
- PasswordConfirm at v1, so approval failed with "Background file access is
1010
- unavailable" and `status`/`deploy` showed `rootOps: unsupported — DSM 103`
1011
- or `DSM 104`. DSM 7.1.1 answers a version above the API's maximum with 104
1012
- (PasswordConfirm v2, TaskScheduler `get` v4) and a method missing at an
1013
- existing version with 103 (TaskScheduler `list` v3). When DSM answers a
1014
- scheduler read or the password check with 103 or 104, the app now uses
1015
- PasswordConfirm v1, TaskScheduler `list` v2/`get` v3 and creates
1016
- the tasks with EventScheduler `create` v1 and TaskScheduler `create` v3.
1017
- The password is still confirmed first and both tasks are still read back
1018
- exactly before anything runs. Other errors (such as 105, 119, 102 or a
1019
- wrong password, 3121) never fall back, and a create is never retried. DSM
1020
- 7.1 does not bind task creation to the password check; see the root-ops
1021
- guide.
1022
- - `status` and `deploy` read the control task the same way.
1023
- - Uninstall on DSM 7.1 now removes the boot and control tasks. The executor
1024
- looked the control task up with TaskScheduler `get` v4, which DSM 7.1.1
1025
- refuses with 104, so it wrote no task inventory and left both tasks behind.
1026
- - If approval stops after it created a task (for example, the boot task was
1027
- created and the control task was not), the app deletes the tasks it created
1028
- in that attempt instead of leaving an enabled root boot task that nothing
1029
- owns. Tasks from an earlier approval stay.
1030
- - A failed approval shows its reason and DSM error code in the app's settings
1031
- instead of only "Background file access is unavailable", and a refused
1032
- password reads as a password failure, not a scheduler failure.
1033
- - After the app's settings approve, the window's status line no longer shows
1034
- "needs-admin-approval: Task is missing" until a reload; the window panel and
1035
- the settings now follow each other's results.
1036
-
1037
- ### DSM 7.1: jobs import and file downloads
1038
-
1039
- - DSM 7.1.1 sends `SYNO.FileStation.Download` v2 replies without
1040
- `Content-Disposition`, so `bg.jobs.submit` and downloads failed with
1041
- `unavailable`. With no such header, a body of at most 4 KiB that decodes
1042
- as a DSM reply is still refused as that reply; any other body is the file.
1043
-
1044
- ### Docs: build your own armv7 Go code with Go 1.26.9
1045
-
1046
- - On armv7 DSM 7.1.1 (kernel 3.10), a program built with Go 1.26.0 crashed
1047
- at start (`futexwakeup returned -22`, SIGSEGV) and Go 1.26.9 ran. Other
1048
- 1.26 releases were not tested.
1049
-
1050
- ### init: next steps install frontend dependencies
1051
-
1052
- - `init -t native-go` with a Vue frontend now lists `(cd frontend && npm ci)`
1053
- before `synopkgland build`; without it the build fails with
1054
- `STANDALONE_UI_BUILD_FAILED`. `build` still does not install dependencies.
1055
-
1056
- ### Executor, daemon and gateway build with exactly Go 1.26.9
1057
-
1058
- - The CLI builds the executor, lifecycle helper, daemon controller and gateway
1059
- with `GOTOOLCHAIN=go1.26.9`, ignoring your `GOTOOLCHAIN`, `GOFLAGS`,
1060
- `GOEXPERIMENT`, `GOWORK` and `GOAMD64`/`GOARM64`, so every builder produces
1061
- the same executor digest. Go 1.21 or later downloads go1.26.9 when needed;
1062
- offline without it the build fails and names go1.26.9. A binary whose Go
1063
- build info names another release is refused.
1064
- - Template go.mod files now say `toolchain go1.26.9`. The executor digest
1065
- changes; it is anchored in INFO, so an upgrade with an unchanged policy needs
1066
- no new approval.
1067
-
1068
- ### INFO arch: four more DSM platforms
1069
-
1070
- - x86_64 SPKs now also list `broadwellntb`, `coffeelake` and `skylaked`, and
1071
- aarch64 SPKs list `rtd1619` (DSM Developer Guide Appendix A family members).
1072
- INFO `arch` now matches the platforms the package source maps to each family.
1073
-
1074
- ### 繁體中文摘要
1075
-
1076
- - 升級後重新建置套件;executor digest 改變。**從 0.7.0 建的套件升級需管理員再核准一次**(0.7.0 的
1077
- executor 開啟套件 `target` symlink 時未跟隨連結,無法自行安裝錨定的 executor);之後 0.7.1 起的
1078
- executor-only 升級不需核准。
1079
- - executor:kernel 3.10(DSM 7.1,如 DS416 armv7)上 executor-only 升級與執行中再核准不再出現
1080
- `pid executable mismatch`;INFO 錨定升級不再誤報 `executor upgrade refused`,也不再先印
1081
- 「paused: 需管理員核准」;`executor.status` 暫停時回 `paused`/`executor_not_listening` 與下一步。
1082
- - 面板:executor 不回應時可再按一次「Approve background access」免密碼重啟;
1083
- `executor_status_mismatch` 會寫出原因(不回應/digest 不同/status 請求失敗);
1084
- 啟動後的 `executor.status` 等待改為 500 ms→2 s 退避,10 s 預算不變。
1085
- - `daemon.timeouts` 預設改為 `{start: 30, stop: 30, status: 5, uninstall: 60}`,新增 `timeouts.uninstall`。
1086
- - `identity: standalone`:DSM 網頁 session(SynoToken)下「Continue with DSM」與 root-ops 核准修正;
1087
- 核准建立 task 後 executor 未啟動時自動撤回本次建立的 task;卸載不再誤記 `task cleanup unconfirmed`。
1088
- - 原生 root window 等待上限改為 10 分鐘並回報進度;`DSM_TLS_UNTRUSTED` 指向 `NODE_EXTRA_CA_CERTS`。
1089
- - `status`/`deploy`:standalone 專案的 status 改用短期 App session;`deploy` 接受
1090
- install-selected delegate 的精靈參數;支援 native layout;卸載記錄 purge timing。
1091
- - DSM 7.1:無 Root API 時改走 PasswordConfirm v1/TaskScheduler v2–v3/EventScheduler create v1,
1092
- 卸載會移除 boot/control task;`SYNO.FileStation.Download` v2 無 `Content-Disposition` 的下載修正。
1093
- - 建置:executor、lifecycle、daemon、gateway 一律以 Go 1.26.9 建置(armv7 kernel 3.10 上 1.26.0 會當機);
1094
- INFO `arch` 新增 `broadwellntb`、`coffeelake`、`skylaked`、`rtd1619`。
1095
- - 測試工具:`scripts/test/executor-bump.py` 收進 repo,會一併改寫視窗腳本內嵌的 executor pin。
1096
-
1097
- ## 0.7.0 (2026-10-10)
1098
-
1099
- Rebuild packages to pick up template, hook and executor changes. The executor
1100
- digest changed: apps approved under an earlier CLI need one administrator
1101
- approval after upgrading.
1102
-
1103
- ### Delegate: save destinations that last until revoked
1104
-
1105
- - `init --with delegate` / `add delegate` now declare `operations:
1106
- [files.moveFromStaging]`, `destinations: [save]`, `lifetime: until-revoked`,
1107
- `scope: install-selected`. Existing TTL declarations (`files.list` …
1108
- `files.write`, `scope: home`) keep their API and limits.
1109
- - The install wizard pre-selects home or a local share and an administrator
1110
- confirms it once. Choosing a destination in the SDK NasPicker grants it until
1111
- revoked, after fresh DSM session, UID and write checks; administrators run as
1112
- their own UID, never root. Read the choice from `delegate.status`
1113
- `selection`, `sdk.destination(key)` or Go `Service.Destination`.
1114
- - Daemon and export producers save through Go `Service.MoveFromStaging`. The
1115
- generated Background saves page (`createBackgroundSaveFacade`) lists, cancels
1116
- and revokes them and includes a working example (`ExportExample`). Daemon can
1117
- combine with an until-revoked delegate worker.
1118
- - Retrying `MoveFromStaging` with the same params never copies twice. Journal
1119
- errors `canceled`, `jobs_busy` and `jobs_unavailable` are retryable;
1120
- `spool_unsafe` and `journal_corrupt` are terminal.
1121
- - Cancel is idempotent per reference and survives executor crash, package
1122
- restart and logout. Revoke, scope change and disable settle copies in flight
1123
- as `canceled` with that reason (for example `scope-changed`); a scope change
1124
- no longer restarts the executor. Poll `ctx.executor.status()` while a change
1125
- answers `blocked`.
1126
- - Full device lifecycle acceptance (install → setup → reboot → upgrade → revoke
1127
- → uninstall) is still pending.
1128
-
1129
- ### Executor
1130
-
1131
- - Upgrades keep approval: each SPK writes its executor SHA-256 into INFO
1132
- (`synopkgland_executor_sha256`), and a new executor that matches it installs
1133
- without PasswordConfirm as long as the policy is unchanged. Policy changes
1134
- (operations, scope, destinations, root scripts) still need approval. `inspect`
1135
- refuses a missing, duplicated or different anchor.
1136
- - A root supervisor restarts the monitor after a crash or kill (1–60 s
1137
- backoff), so delegate and rootOps recover without a reboot.
1138
- - Every write publishes only through the executor handshake. A copy whose
1139
- executor died stops within 1 MiB and removes its `.part`; a lost publish
1140
- receipt settles `committed` without copying again.
1141
- - A package without an executor can add one in place on upgrade. Upgrade does
1142
- not approve an install-selected scope; confirm it in Settings.
1143
- - Uninstalling a stopped package no longer leaves root tasks and the monitor
1144
- running for ten minutes; the hook waits at most 50 s for the executor to clean
1145
- up.
1146
-
1147
- ### Native layout: per-architecture INFO trust anchors
1148
-
1149
- - `layout.anchors` maps `<key>_sha256` to a payload file. `build` writes that
1150
- architecture's SHA-256 of the file into INFO, so a product root helper can
1151
- trust a new build through the root-owned INFO instead of reapproval. Keys
1152
- match `^[a-z][a-z0-9_]{0,48}_sha256$`, the `synopkgland_` prefix is reserved,
1153
- and at most 16 are allowed. `inspect` refuses a changed, duplicated, missing
1154
- or undeclared anchor.
1155
-
1156
- ### App entry and Application Privileges
1157
-
1158
- - `entry: admin-only` admits administrators only, on every route including the
1159
- SDK routes; ordinary users get `admin_required`. `entry: app-privilege`
1160
- registers the app in Control Panel > Application Privileges and gates every
1161
- route on DSM's decision (`app_privilege_denied`). Omitting `entry` keeps the
1162
- previous behaviour and byte-identical SPKs.
1163
- - With an `entry`, an app route marked `authenticated` or `public` fails with
1164
- `STANDALONE_ENTRY_WIDENED`. `inspect` prints the entry mode and flags
1165
- `ENTRY_LAUNCHER_MISMATCH`.
1166
- - The executor applies the same rule: moves for a user DSM no longer allows are
1167
- refused (`APP_DENIED`).
1168
- - `ctx.user = {name, isAdmin}` lets UIs hide administrator settings; guard them
1169
- on the server with Go `api.RequireAdmin(caller)`.
1170
- - On Linux 3.10 kernels without `memfd_create` (DSM 7 on x86_64 avoton,
1171
- braswell, bromolow, grantley) the administrator and App Privileges bridges run
1172
- from an unlinked file in the package's own tmp instead of failing. That path
1173
- has no memfd seal. Verified on DSM 7.1.1 with an armv7 build only; no x86_64
1174
- Linux 3.10 model has been tested.
1175
-
1176
- ### Application Portal
1177
-
1178
- - `portal: {}` or `portal: {alias}` (native) and `integration.web.portal`
1179
- (Compose DSM-window apps); `synopkgland add portal` adds it. The install
1180
- wizard prefills the alias, upgrades keep it and an administrator activates it.
1181
- - `ctx.portal.mountPanel(element)` shows the link, or the alias form to
1182
- administrators; the Vue and plain starters mount it.
1183
- - A portal needs `os_min_ver` 7.3.2-86009 or later (DSM 7.1.1 rejects it with
1184
- error 320; 7.2 is untested). validate/build fail with
1185
- `STANDALONE_PORTAL_MIN_DSM` / `CONTRACT_PORTAL_MIN_DSM`, and `add portal`
1186
- raises a lower `os_min_ver`.
1187
-
1188
- ### Catalog metadata
1189
-
1190
- - An optional `metadata:` block (contact/support email, homepage, source and
1191
- issues URLs, license, localized long description, changelog, screenshots) is
1192
- validated and written to INFO for the package catalog.
1193
- - `metadata.changelog` may be a per-language map. Package Center shows one line
1194
- in the NAS language, so put the most important change first.
1195
-
1196
- ### Native authorization
1197
-
1198
- - `authorization_required` gains `reason: upgrade_reapproval` when an upgrade
1199
- revoked the old grant; the Gate template explains the next step in nine
1200
- languages. Products that sync the Gate need their helper to write and clear
1201
- the reason-only tombstone, which grants nothing.
1202
-
1203
- ### Lifecycle
1204
-
1205
- - Background packages restart a package user systemd manager that lost its
1206
- runtime socket; a failed worker start names the unit, exit code and socket
1207
- state.
1208
- - Stopping the worker is idempotent, fixing uninstall failure 314 for
1209
- delegate+daemon+jobs.
1210
-
1211
- ### Templates in nine languages
1212
-
1213
- - All template UI text and install, upgrade and uninstall wizard messages come
1214
- from catalogs in the nine DSM languages and follow DSM glossary wording. Sizes
1215
- use DSM's 1024-based KB/MB/GB. Rebuilt Vue and binary SPKs change bytes;
1216
- plain-UI SPKs are unchanged.
1217
- - Vue templates pin public `@synopackageland/ui@0.8.1`; the upload FilePicker
1218
- states `max 16 GB`.
1219
-
1220
- ### armv7 (32-bit ARM) for native-go and binary packages
1221
-
1222
- - `arches` accepts `armv7` (INFO `alpine alpine4k armada38x monaco`, DSM 7.2+);
1223
- the default stays `[x86_64, aarch64]`. It covers the Go CGI (plain or Vue UI),
1224
- `backend.kind: binary`, jobs, daemon, root-ops and delegate. Container
1225
- projects refuse it with `CONTAINER_ARCH_UNSUPPORTED`.
1226
- - The plain native-go CGI was verified on a DS416 (DSM 7.1.1, `alpine4k`).
1227
- armv7 jobs, daemon, executor and lifecycle helper are built and checked but
1228
- not yet verified on a NAS; `armada38x` and `monaco` are untested.
1229
- - `deploy` detects armv7 from an Annapurna Labs CPU and asks for `--arch` on a
1230
- Marvell CPU. x86_64 and aarch64 bridge, executor and lifecycle bytes are
1231
- unchanged by the armv7 work.
1232
-
1233
- ### Packaging
1234
-
1235
- - The npm tarball includes this CHANGELOG.md.
1236
-
1237
- ### 繁體中文摘要
1238
-
1239
- - 升級後重新建置套件;executor digest 改變,舊版 CLI 核准過的 app 需要管理員再核准一次。
1240
- - delegate:預設改為長期存檔目的地(`files.moveFromStaging`、安裝時選 scope、管理員確認一次、
1241
- 在 NasPicker 選目的地即授權至撤銷);Background saves 頁、`MoveFromStaging` 重送不重複複製、
1242
- 取消跨重啟保留。完整裝置生命週期驗收仍待完成。
1243
- - executor:INFO 錨定 executor SHA-256,policy 不變的升級免再核准;root supervisor 異常後自動重啟;
1244
- 卸載已停止的套件不再殘留 root task。
1245
- - 原生 layout:`layout.anchors` 依架構把 payload 檔 SHA-256 寫進 INFO,作為產品 root helper 的信任錨點。
1246
- - `entry: admin-only | app-privilege`(DSM 應用程式權限)、`ctx.user.isAdmin`、`api.RequireAdmin`;
1247
- 未宣告 entry 的 SPK 不變。Linux 3.10 無 memfd_create 時 bridge 改由套件 tmp 執行(無 memfd seal)。
1248
- - Application Portal(`portal:`、`add portal`、`ctx.portal`),需 os_min_ver 7.3.2-86009 以上;
1249
- 目錄 `metadata:` 寫入 INFO,`metadata.changelog` 可依語言分開。
1250
- - 授權契約新增 `reason: upgrade_reapproval`;背景套件 user manager 恢復、卸載 314 修正。
1251
- - 範本與安裝精靈全部九語系並依 DSM 用語;Vue 範本固定 UI 0.8.1。
1252
- - armv7(DSM 7.2+)支援 native-go 與 binary;plain CGI 已在 DS416 實機驗證,jobs/daemon/executor 尚未實機驗證。
1253
- - npm 套件附 CHANGELOG.md。
1254
-
1255
- ## 0.6.1
1256
-
1257
- - Restore native product packaging as a formal schema 2 `backend: {kind: native}`
1258
- route. Keep project-owned multi-binary payload/metadata layout, prepare/compile
1259
- argv hooks, HTTPS/SHA-256 toolkit acquisition and x86_64/aarch64/armv8/armv7.
1260
- `check`, `build` and `inspect --json` preserve package-only lifecycle/systemd
1261
- registration and reject root service accounts, privileged execution prefixes,
1262
- symlinks, setuid files and corrupt toolkit/payload inventories.
1263
- - Restore `@synopackageland/cli/authorization-sdk` as an explicit native-product
1264
- adapter with exact pinned-task identity, fresh administrator approval and
1265
- withdrawal-before-task-removal; do not change generated Go executor semantics.
1266
- Export `./native-layout` and retain direct `./dist/*` imports.
1267
- - Document field migration from schema 1 without changing product bootstrap pins,
1268
- per-build revocation/reapproval or dynamic DSM ABI declarations. Schema 1 remains
1269
- rejected; no compatibility aliases for retired CLI commands. Generated schema 2
1270
- check/build/status/logs and Connect/Gopeed APIs remain unchanged.
1271
- - Align SKILL.md with the shipped delegate/rootOps executor implementation and
1272
- its documented device acceptance boundaries.
1273
- - 正式補回 schema 2 原生產品 layout/hooks/toolkit/三架構及 authorization SDK。
1274
- lifecycle/systemd 維持 package account;root 僅由產品明確核准 digest,
1275
- 不自動授權、不取消逐 build 撤權再授權。新增 Reclaim 欄位 migration 指引。
1276
-
1277
- ## 0.6.0
1278
-
1279
- - Adopt UI 0.8.0 and its renamed `syno-pkg-land-ui` executable (no old alias).
1280
- Pin generated Vue apps and all overlays to the public npm artifact and integrity.
1281
- Skills discover the installed prompt/catalog/describe contract with
1282
- `npx --no syno-pkg-land-ui`; one-off use explicitly selects the npm package.
1283
- Render defaults to `syno-pkg-land-ui-render`. CSS/data selectors use the
1284
- `syno-pkg-land-ui` namespace without old aliases (including select attributes).
1285
- Add a regression audit for executable names and retired CSS/data selectors in
1286
- skills, templates, source, tests, docs and scripts; only released history is exempt.
1287
- 跟進 UI 0.8.0 的 CLI 改名及公開 npm integrity;AI 指引維持版本無關,
1288
- 使用已安裝契約,並防止指令名與 selector 回退。預設截圖目錄改為
1289
- `syno-pkg-land-ui-render`;CSS/data selector 全面使用新 namespace,不保留 alias。
1290
-
1291
- - Add version-independent Vue UI guidance alongside JSON UI guidance. All
1292
- generated projects include AGENTS.md and a CLAUDE.md pointer to discover the
1293
- bundled skills and run local check/build/inspect before authorized NAS work.
1294
- Template design notes use installed UI contracts while dependency pins remain.
1295
- 新增 Vue UI skill;所有 scaffold 提供 AI 入口與本機驗證流程,設計文件依已安裝
1296
- SDK 契約,依賴鎖定不變。
1297
-
1298
- - **Breaking: remove `./authorization-sdk` and the legacy root authorization
1299
- templates and adapters.** Use delegate/root-ops capabilities and the protected
1300
- executor with one-time administrator approval; no compatibility exports remain.
1301
- **不保留相容**:移除 `./authorization-sdk` 與舊 root authorization 模板/adapter,
1302
- 改用 delegate/root-ops 能力及受保護 executor 的一次管理員核准。
1303
-
1304
- - Pin generated Vue apps to public `@synopackageland/ui@0.8.0` with its npm
1305
- integrity; keep bounded app-local modal roots, embedded CSS and unique mount
1306
- ID prefixes, and explicitly retain DSM window layout at wide sizes.
1307
- Add `skill show ui-json` with installed prompt/catalog/schema discovery,
1308
- strict validation and screenshot/layout checks through the published `syno-pkg-land-ui`
1309
- package bin. Update the template
1310
- DOM test dependency and website configuration to remove gate warnings.
1311
- Vue 模板固定公開 UI 0.8.0 與 integrity,維持 DSM 的 app scope、embedded CSS、
1312
- 唯一 mount ID 與 window layout;新增不固定版本的 JSON UI skill,依已安裝
1313
- catalog/schema 產生、嚴格驗證並 render,直接使用套件的 `syno-pkg-land-ui` bin。
1314
-
1315
- - **Delegate (C), P2/v10 (through 26030e74):** `init --with delegate` / `add delegate` imply jobs;
1316
- manifest operations use the five fixed files.* IDs with home/shared scope.
1317
- The worker stages downloads and writes as the consenting user after browser
1318
- close, without overwriting existing files or retaining DSM credentials.
1319
- C/R share one-password boot/control task setup and digest approval; stop pauses,
1320
- start resumes identical pins, changed policy requires reapproval, and uninstall
1321
- requests asynchronous own-task/protected-state purge (normally seconds with a
1322
- healthy monitor), with manual cleanup guidance. Task deletion requires byte-for-byte
1323
- equality with canonical scripts regenerated from package identity and approved pins.
1324
- Purge reapproval recreates tasks and writes `start` before boot; mailbox failure
1325
- blocks boot. Local fake-DSM/real-executor lifecycle tests cover this recovery.
1326
- Package-directory/INFO identity absence immediately pauses the socket/requests,
1327
- retaining the monitor, approved runtime, delegations and tasks. Polling every
1328
- 500 ms must confirm 10 continuous minutes of absence before fallback purge;
1329
- reappearance or unverifiable absence resets the timer, with uncertainty staying paused.
1330
- Recovery rereads even an unchanged mailbox: matching-pin `start` resumes,
1331
- changed pins stay paused/needs-admin-approval, `stop` stays paused, and `purge`
1332
- immediately clears state. Temporary DSM-upgrade gaps preserve protected state;
1333
- preuninst purge remains immediate, with asynchronous cleanup. Boot's absent-package
1334
- retained-pin purge remains immediate. Local tests cover upgrade gaps, recovery,
1335
- timer/reset rules and immediate preuninst purge; device acceptance remains separate.
1336
- Malformed AppPriv responses fail closed, including duplicate keys/target entries,
1337
- null/missing data and nested error keys. Matching token header aliases are allowed;
1338
- conflicting/repeated headers, duplicate id cookies and controls are refused.
1339
- Empty/partial caller auth never falls back to worker revoke. Job admission reserves
1340
- before creating grants outside the journal lock; retry `jobs_busy` with the same key.
1341
- Writes fsync an exclusive hidden temporary then publish a hard link without overwrite;
1342
- cancellation cleans the temporary and leaves no partial final file.
1343
- Add bilingual developer and administrator guides covering typed Files, stable
1344
- errors, AppPriv defaults/denies and DSM-retained directories/accounts.
1345
- Full device lifecycle acceptance remains separate from source/local tests.
1346
- Delegate 隱含 jobs;以本人身分離線寫檔、不覆寫、不保存 DSM 憑證。
1347
- C/R 共用一次密碼設定與 digest 核准;中英文文件說明委託/撤銷、
1348
- typed Files、穩定錯誤碼、paused/purge、手動清理與 DSM 保留界線。
1349
- AppPriv 異常回應 fail-closed;token alias 同值允許、衝突/重複及控制字元拒絕,
1350
- 空/不完整 caller auth 不降級為 worker。`jobs_busy` 沿用相同 key 重試。
1351
- 隱藏暫存檔 fsync 後以 hard link 發布,不覆寫、取消不留半個正式檔。
1352
- 特權清理非同步,monitor 正常時通常數秒完成;刪 task 前完整 script 須逐字相符。
1353
- Purge 後重新確認密碼、重建 task,boot 前 mailbox 重設 `start`;寫入失敗不執行 boot。
1354
- 套件目錄/INFO identity 消失立即關 socket、取消/drain 請求並暫停,保留 monitor、
1355
- 核准 runtime、委託與 task;每 500 ms 輪詢確認連續消失達 10 分鐘才後備 purge。
1356
- 重新出現或無法確認消失皆重設計時;無法確認時維持 paused。
1357
- 恢復時重讀未變 mailbox:`start` 且 pins 相同才恢復,pins 改變維持 paused/
1358
- needs-admin-approval,`stop` 維持暫停,`purge` 立即清理。DSM 升級短暫空窗不誤清;
1359
- preuninst purge 仍立即處理、非同步清理;boot 套件不存在時驗 retained pins 立即 purge 不變。
1360
- 本機測試涵蓋升級空窗、恢復、計時/重設及 preuninst 立即 purge,不代表裝置驗收。
1361
-
1362
- - **Breaking: schema 2 and task-based CLI, with no compatibility layer.**
1363
- Native-go and binary now use dsm-package.yaml schema 2 with backend.kind and
1364
- declared capabilities. Use init → check → build → deploy → status, positional
1365
- inspect <spk> and logs. The former build-spk/validate/info/log commands, native
1366
- layout/native.yaml, native privilege/background/fileStation fields and old
1367
- capability flags are removed; there are no aliases or migration shims.
1368
- Schema 2 uses --with files,jobs,delegate,login,root-ops or add to extend native-go:
1369
- private owner-bound data/jobs (A), live caller-session files/tree/transfers (B),
1370
- standalone identity (I), offline delegation (C), and fixed administrator
1371
- operations (R). C/R share one-time protected executor approval.
1372
- Manifest delegate.operations uses
1373
- files.list/files.info/files.createFolder/files.checkPermission/files.write
1374
- directly. Check/build/inspect/deploy/status support structured JSON results.
1375
- Compose retains its separate contract and uses init -t compose, optionally
1376
- importing with --from. No publish or signing command is added.
1377
- **不保留相容**:native-go/binary 改用 schema 2;CLI 依工作流程改名,
1378
- 移除舊欄位、旗標、指令與 native layout,不提供 alias 或 shim。
1379
- A/B/I/C/R 以能力宣告選用;C/R 共用 executor 一次核准。
1380
-
1381
- - Generate background submit/schedule keys with cryptographic getRandomValues,
1382
- which works on DSM HTTP; do not require secure-context randomUUID.
1383
- 背景工作及排程以 getRandomValues 產生請求鍵,可在 DSM HTTP 使用;
1384
- 不再依賴僅限安全環境的 randomUUID。
1385
-
1386
- - Give interactive File Station uploads their own bounded twenty-minute
1387
- response-header deadline without changing the shared metadata transport.
1388
- 互動 File Station 上傳使用獨立的二十分鐘回應標頭期限,不修改共用查詢 transport;
1389
- 保留目前 session、不覆寫、不重試及寫入結果不確定契約。
1390
-
1391
- - Frame native-Go CGI JSON with exact byte lengths, including Unicode and
1392
- refusal/serialization failures; retain DSM's HTTP-200 JSON error convention.
1393
- native-Go CGI JSON(含 Unicode、拒絕及序列化失敗)提供精確位元組長度,
1394
- 並保留 DSM 的 HTTP 200 JSON 錯誤契約。
1395
-
1396
- - Replace R2 publication with a private GitHub catalog and streamed Release SPKs.
1397
- - Serve live bilingual catalog pages and architecture-specific manual downloads.
1398
- - Remove the two web demo templates; retain native-go, binary and the single compose template.
1399
-
1400
- ## 0.5.3
1401
-
1402
- - Ship the native-go root authorization template with build-pinned plans,
1403
- the canonical Vue gate and nine language catalogs, explicit native approval,
1404
- exact disabled tasks, bounded legacy terminal proof and lifecycle withdrawal.
1405
- - Export `@synopackageland/cli/authorization-sdk` for reviewed native-layout
1406
- adapters to consume the same host runtimes and canonical template resources.
1407
- - Let an explicitly supplied native-layout withdrawal callback finish before
1408
- removing tasks; failed or missing callbacks abort withdrawal.
1409
- - Legacy DSM authorization has no server-enforced one-use create token;
1410
- modern DSM keeps its native one-use PasswordConfirm token contract.
1411
- - Isolate packed Docker runtime smoke in temporary share directories and its
1412
- own Compose project, including owned-volume cleanup. The 0.5.1 release tag
1413
- did not publish because the existing smoke omitted installer share values.
1414
- - Use a pinned long-running imported runtime fixture for readiness smoke;
1415
- the default hello-world demo exits immediately. The 0.5.2 workflow was
1416
- cancelled before publication when CI exposed that fixture race.
1417
- - Retain bounded artifact-test diagnostics up to eight MiB and propagate
1418
- real frontend test failures rather than hitting Node's default buffer limit.
1419
-
1420
- ## 0.5.0
1421
-
1422
- - Add opt-in native layout packaging for existing products: argv-only build hooks,
1423
- checksum-pinned HTTPS toolkit caching, custom package-user metadata/payload
1424
- layouts, explicit ELF linkage assertions, DSM armv8/armv7 and build overrides.
1425
- - Inspect native layouts with complete payload hash/mode and INFO checksum checks;
1426
- generated templates retain their existing privilege and architecture contracts.
1427
-
1428
- - **GitHub CI and tag-based npm releases** (CI-LAND). Add shared full gates, Docker
1429
- runtime/tarball smoke and credential-free release dry runs for both npm packages.
1430
- Version tags require matching metadata, default-branch ancestry, an unpublished
1431
- version and versioned CHANGELOG notes. Push scripts now use the existing GitHub
1432
- origin; `push:all` preserves the separate Community Apps synchronization.
1433
-
1434
- - **Vue templates pin UI 0.5.7 and let the UI text follow the app locale**
1435
- (TEMPLATE-UI-057). Every `native-go --ui vue` project pins
1436
- `@synopackageland/ui@0.5.7` (integrity `sha512-egHKoU4J...`). Each app root now wraps
1437
- its content in `ConfigProvider locale`, set from the DSM language by the template's
1438
- existing `uiLocale()` mapping, so the UI's built-in text follows it in the nine
1439
- built-in locales. The template drops the per-control `FilePicker` locale and the
1440
- `Link` `download-label` that only repeated those defaults; a template test requires
1441
- the provider and rejects those overrides. 0.5.7 also resolves `FormItem`
1442
- `direction="auto"` by the item's own width, stacks `SettingsRow` below 520px and
1443
- keeps `KeyValueRow` values wrapping; the template needed no mitigation for any of
1444
- these. Existing 0.5.6 projects upgrade by regenerating, or by changing the pin and
1445
- re-running `npm ci`.
1446
- - **Vue templates use the UI controls instead of native form elements**
1447
- (TEMPLATE-SELECT). The job form's worker and operation pickers and the
1448
- background schedule interval are now `Select`; the schedule consent is a
1449
- `Checkbox`; the job, schedule, sample-note and folder forms are `Form` +
1450
- `FormItem` + `FormActions` (vertical, controls capped at `md`), and the
1451
- hand-written `.integration-field` label CSS is gone. Labels are wired to
1452
- `Input` and `Textarea` through the FormItem control id. A template test now
1453
- fails when any `.vue` file renders a native `<select>`, `<option>`,
1454
- `<textarea>` or `<label>`, or a `FormItem` outside a vertical `Form`. Three UI
1455
- gaps remain and are not worked around with custom components: `Select` does
1456
- not take the FormItem control id (each Select carries a labelled
1457
- `role="group"` so it keeps an accessible name), the upload stays a native
1458
- `<input type="file">` because `FilePicker` hard-codes English "Choose files" and
1459
- "max" text, and the file download keeps a native `<a>` (no link component).
1460
- Existing projects pick this up only by regenerating; nothing changes in the
1461
- manifest, routes or backend.
1462
- - **Tarball smoke covers the default Vue UI and explicit plain UI**
1463
- (SMOKE-PACK-VUE). Install each generated Vue frontend with its lockfile before
1464
- building; keep both architectures and explicit root fixtures, then binary and
1465
- container packaging. The container smoke now selects `--arch x86_64` for
1466
- `-o app.spk` and its deployment plan, as explicit output requires one arch.
1467
- Docker runtime QA is opt-in with `smoke-pack.sh --docker`
1468
- and is explicitly skipped otherwise. Failed npm UI builds without installed
1469
- dependencies now name the UI directory and suggest `npm ci` (or `npm install`
1470
- without a lockfile) before retrying `build`; builds never install implicitly.
1471
-
1472
- - **Vue templates pin UI 0.5.5 and use PageContent** (TEMPLATE-UI-055). Every
1473
- `native-go --ui vue` project pins `@synopackageland/ui@0.5.5` (integrity
1474
- `sha512-pLQmZqNH...`). The sidebar shell now wraps the page header and the page
1475
- content in the library `PageContent`, so a maximised window keeps both on the
1476
- same left and right edges; the page area scrolls inside it. 0.5.5 also adds
1477
- `CardGrid`, `FormActions`, `RepeatRows` and `fieldWidth`; the template has no
1478
- card lists or custom width CSS, so it needs none of them. Default change to
1479
- know when upgrading: form controls are capped at `md` (320px) unless you pass
1480
- `field-width="full"`. Existing 0.5.4 projects upgrade by re-running `npm ci`
1481
- after the pin change.
1482
-
1483
- - **Vue templates pin UI 0.5.4** (TEMPLATE-UI-054). Every `native-go --ui vue`
1484
- project now pins `@synopackageland/ui@0.5.4` (integrity `sha512-mzuGUO+8...`).
1485
- 0.5.4 is visual only and changes nothing in the template source: under the
1486
- embedded theme, Card, EmptyState, PageHeader and WizardStep titles no longer
1487
- carry a default margin, the expand buttons of SettingsSection, Card and
1488
- ExpandableItem lose their grey box, and dashed separators draw only the bottom
1489
- edge. Existing 0.5.3 projects upgrade by re-running `npm ci` after the pin change.
1490
-
1491
- - **Root tasks retain DSM 7.1 support.** Probe `list v3` and use the legacy
1492
- `create v3`, `list v2`, `get v3`, `run/delete v2`, and `view v1` only on code
1493
- 103. Preserve native password confirmation while distinguishing tokenless
1494
- success from cancellation; DSM 7.1 has no server-side root-creation password
1495
- token. Require a new successful completion record within a bounded deadline,
1496
- exact task identity, and no mutation retries. Root init, validation, and build
1497
- keep a consistent minimum of DSM 7.1-42661.
1498
- - **Vue templates pin UI 0.5.3** (TEMPLATE-UI-053). Every `native-go --ui vue`
1499
- project pins `@synopackageland/ui@0.5.3` in `frontend/package.json` and its
1500
- npmjs lockfile (integrity `sha512-ieKdf0nB...`). 0.4.2 to 0.5.3 is additive for
1501
- the template: new components (Select search and groups, Combobox,
1502
- MultipleSelect, SuperboxSelect, Grid/Row/Col, Space, Affix, Breadcrumb,
1503
- Pagination, SegmentControl, Card, Panel, Fieldset, Form, Notification, and more),
1504
- DataGrid grouping/expansion/reorder, checkable and draggable TreeView, DropdownMenu
1505
- submenus, and `Input`/`Dialog`/`SettingsSection`/`Callout` options with unchanged
1506
- defaults. Visual changes: Tabs and SectionTabs share a sliding marker with a
1507
- 160ms panel fade, the Spinner is a full rotating ring (both honour
1508
- `prefers-reduced-motion`), selected tab labels are blue, and the DataGrid footer
1509
- draws both borders and fills its flex container. The template needed no API
1510
- change; it now uses the library `Checkbox` and `Input` for the consent and limit
1511
- fields instead of native inputs. Existing 0.4.2 projects upgrade by re-running
1512
- `npm ci` after the pin change.
1513
-
1514
- - **Templates never copy a local `node_modules`/`dist`** (TEMPLATE-COPY). A
1515
- template checkout used for development could leak its own install directory or
1516
- build output into a generated project, shadowing the project's `npm ci`.
1517
-
1518
- - **Vue templates pin UI 0.4.2** (TEMPLATE-UI-042). Every `native-go --ui vue`
1519
- project pins `@synopackageland/ui@0.4.2` in `frontend/package.json` and its
1520
- npmjs lockfile. 0.4.1/0.4.2 are visual, backward-compatible control changes with
1521
- no API change: controls share the `--ui-control-*` tokens and
1522
- `--ui-radius-control`, so overriding `--ui-radius-sm`/`--ui-radius-md` no longer
1523
- reshapes them; a disabled grey button keeps its surface, a loading button keeps
1524
- full strength, and the Switch track padding is reset. Existing 0.4.0 projects
1525
- upgrade by re-running `npm ci` after the pin change.
1526
-
1527
- - **Private-copy background schedules (S1-a)**: hourly/daily/weekly owner-scoped
1528
- creation, disable/delete, 8-definition limit, durable atomic trigger checkpoints,
1529
- missed-without-catch-up recovery, revision mismatch disable and explicit admin
1530
- re-enable. The Vue panel covers nine locales. Imported/pinned copies count toward
1531
- quotas; completed results never replay and owner purge also removes schedules.
1532
- Package account only, no stored credentials or offline personal ACL access.
1533
- Evidence: local gates and vt-nas DSM 7.4 x86_64 controlled-due-time tests
1534
- (two runs per owner, stop/start, +1 upgrade, disable and fresh normal1 browser
1535
- readback). This is not an hour-long cadence or reboot guarantee.
1536
-
1537
- - **Minimum DSM 7.1 and multiple architectures** (DSM-COMPAT). Every template now declares
1538
- `os_min_ver: "7.1-42661"` (DSM 7.1; before: `7.2-64570`, `7.2-69057` or `7.2-60000`).
1539
- `init --min-dsm` and `build --min-dsm` take `7.0`, `7.1`, `7.2` or a full
1540
- `7.x-build` to override it. `build --arch` accepts a comma-separated list
1541
- - **Per-runtime minimum DSM and multiple architectures** (DSM-COMPAT, DSM-COMPAT-2).
1542
- `native-go` and `binary` declare `os_min_ver: "7.1-42661"` (DSM 7.1; before:
1543
- `7.2-64570`, `7.2-69057` or `7.2-60000`). The container templates declare
1544
- `7.2.1-69057` (DSM 7.2.1), and `init --min-dsm`/`build --min-dsm` refuse a
1545
- container value below it (`MIN_DSM_CONTAINER_FLOOR`) because Container Manager's
1546
- docker-project worker only exists from DSM 7.2.1; `inspect` reports the same
1547
- gap on an already-built container SPK. `--min-dsm` takes `7.0`, `7.1`, `7.2`,
1548
- `7.2.1` or a full `7.x-build`. `build --arch` accepts a comma-separated list
1549
- (`x86_64,aarch64`); without it every family is built, one SPK per family (binary and
1550
- gateway container Apps used to require `--arch`). `inspect` flags an INFO `arch`
1551
- that mixes families or names an unknown platform, a CGI built for another family than
1552
- INFO declares, and an `os_min_ver` that is not a DSM 7 `<version>-<build>`. The
1553
- x86_64 INFO platform list now includes `kvmcloud`, `epyc7003` and `epyc7003ntb`.
1554
- Only DSM 7.4 x86_64 has been run.
1555
- - **Ordinary-user background jobs (U1)**: administrators may independently enable
1556
- `allowOrdinaryUsers`. Fresh DSM username/UID ownership isolates list/get/cancel/result,
1557
- counts and idempotency keys; unknown UIDs fail closed. Per-owner admission is bounded
1558
- to 2 pending jobs and 1 MiB including reserved results, under the same spool lock.
1559
- Administrators can cancel all jobs and explicitly purge an exact owner with durable
1560
- audit and running-job drain. The Vue panel and all nine locales cover both roles.
1561
- No root or stored user credentials; S1-a adds private-copy schedules below.
1562
-
1563
- - **Container gateway directory groups** (GW-DIRECTORY-GROUPS). Accounts absent
1564
- from `/etc/passwd` now resolve groups through DSM NSS using the fixed `/usr/bin/id`
1565
- as the package account. Qualified `DOMAIN\group` and `group@domain` names retain
1566
- their exact domain and case, including internal spaces; lookup failure denies.
1567
- The SDK caches nothing (TTL 0); DSM NSS caches and already-open streams remain
1568
- limitations. AD resolver allow/deny checks passed in a non-root DSM 7.4 package
1569
- CGI; LDAP, AD-user browser login and group-removal propagation remain unverified.
1570
- Rebuild container SPKs to obtain the change.
1571
-
1572
- - **`synopkgland images pin` resolves and writes container image digests**
1573
- (IMAGE-DIGEST-CLI). It reads every service image in `compose.yaml`, asks the
1574
- registry for the current multi-arch manifest digest over the OCI Distribution
1575
- API (anonymous Bearer token flow for Docker Hub and ghcr; no Docker daemon or
1576
- `buildx`), and rewrites the reference to `tag@sha256:…` in place, keeping
1577
- comments and file layout. `--check` reports unpinned and stale pins without
1578
- writing and exits non-zero. `inspect`'s unpinned-image finding now points at
1579
- the command. Evidence: local unit tests against a fake registry plus one manual
1580
- Docker Hub resolution of `busybox:1.36`; not verified on a real DSM.
1581
- - **Container upgrade wizard no longer breaks on an untouched Run-as control; numeric
1582
- command arguments are rejected for every Compose route** (WIZARD-LIVE-CHECK).
1583
- Measured on vt-nas (DSM 7.4-90069) with the `compose-import` template: an untouched
1584
- upgrade submitted `RUN_AS` pre-filled with the name of the first Shared Folder, `preinst`
1585
- failed, and DSM, which removes the old version before it runs the new `preinst`, left the
1586
- package not installed (data kept). A `dsm_user` now has the same "Change Container Run-as"
1587
- box as a Shared Folder: on an upgrade the submitted user only counts when that box (or
1588
- `deploy --run-as`) is set; otherwise the installed user is kept. Also on vt-nas: the
1589
- Docker Project worker drops YAML quotes, so `command: ["sleep", "3600"]` ended the
1590
- install as broken (`failed to acquire postinst worker`). `synopkgland check` and
1591
- `build` already rejected that on the static route; the dynamic route (install
1592
- parameters, which includes `compose-import`) now rejects it too
1593
- (`COMPOSE_NUMERIC_COMMAND_ARG_FORBIDDEN`).
1594
- - **`install.sharedFolder`** (T-0120): native-go packages can declare a Shared Folder (`select` wizard or `create`
1595
- via DSM `data-share`), stored by name in `var/config.json`; `init --shared-folder select|create` and an `inspect`
1596
- check are included. In `select` mode the administrator grants the package account access afterwards.
1597
- - **Container gateway accepts a query SynoToken; known limit with DSM CSRF protection on**
1598
- (GW-QUERY-TOKEN). On `GET`, `HEAD` and `OPTIONS` with an empty `X-SYNO-TOKEN`, the
1599
- gateway hands a `?SynoToken=` value to `authenticate.cgi` (header wins; still stripped
1600
- upstream); state-changing methods are unchanged. Measured on DSM 7.4 with "Improve
1601
- protection against cross-site request forgery attacks" on: opening a container App
1602
- from the DSM desktop sends no token at all, so the gateway answers `401`
1603
- `session_required`. Known limitation: turn the setting off or use the `native-go`
1604
- template.
1605
- - **Container gateway: readable errors, SSE and WebSocket** (GW-ERRORS-WS). DSM's web
1606
- server replaces the body and headers of any CGI reply with status `403`, `404`,
1607
- `500`, `502`, `503` or `504` by its generic error page (measured on DSM 7.4-90069;
1608
- every other status passes whole). The gateway therefore no longer sends those
1609
- statuses itself: `forbidden` and `token_required` arrive as `401`, a stopped
1610
- container as `521` with `Retry-After`, a timeout as `524`, an invalid reply as
1611
- `520`; every gateway error carries `X-Synopkgland-Error` (the code) and, for a
1612
- stand-in, `X-Synopkgland-Status` (the status it replaces). **Behaviour change:**
1613
- those statuses were `403`/`503`/`504`/`502`/`500` before. A request header
1614
- `X-Synopkgland-Keep-Error-Body: 1` makes the same swap for the container's own
1615
- error replies so an App can read its JSON error bodies. Server-Sent Events pass
1616
- through unbuffered and **WebSocket now works** (it answered `501`): DSM hands both
1617
- directions of an upgraded connection to the CGI, which the gateway uses; the
1618
- handshake must pass the same same-origin check as a state-changing request
1619
- (`cross_site_blocked`), because a browser cannot add `X-SYNO-TOKEN` to a handshake. Other protocol upgrades stay
1620
- `501`. See [Container access control](/docs/guides/container-access/).
1621
- - Fix: **`dsm-groups` with `groups: [users]` let nobody in.** DSM keeps no member
1622
- list for `users` (every local account has it as its primary group), so the gateway,
1623
- which only read member lists from `/etc/group`, answered `403` to everyone, including
1624
- administrators (reproduced on vt-nas). The gateway now also counts the primary gid
1625
- from `/etc/passwd` as membership and fails closed if it cannot read that file.
1626
- Rebuild the SPK to pick it up. Real-account `dsm-groups`/`dsm-admins` checks passed on
1627
- vt-nas (x86_64); the aarch64 gateway is verified in an arm64 Linux VM only, not on an
1628
- aarch64 DSM NAS.
1629
- - **Gateway CSRF check no longer relies on the SynoToken value** (GW-TOKEN). On a NAS
1630
- where DSM's "Improve protection against cross-site request forgery attacks" is off (vt-nas: `enable_syno_token="no"`), DSM
1631
- issues one fixed placeholder token to every session and validates nothing. The container
1632
- gateway now also requires `Sec-Fetch-Site: same-origin` and/or an `Origin` equal to the
1633
- request `Host` for `POST`/`PUT`/`PATCH`/`DELETE` (answer `cross_site_blocked`, sent as `401` because DSM replaces `403` bodies) and a
1634
- well-formed `X-SYNO-TOKEN`; safe methods are unchanged. Browsers that send neither
1635
- header on a state-changing request are refused. Verified on a real DSM 7.4 as an
1636
- administrator and an ordinary user (foreign `Origin`, `null`, `Sec-Fetch-Site`
1637
- cross-site/same-site, missing evidence refused; same-origin passes). The token path with
1638
- the DSM setting on is untested. Rebuild container SPKs to pick it up. See
1639
- [Container access](/docs/guides/container-access/).
1640
- - **Container templates pin their image by digest** (CONTAINER-PULL): `hello-files` and `compose-import` now use
1641
- `image: <tag>@sha256:…`. `build` writes `force_pull: false` into the
1642
- `docker-project` resource when every service image is digest-pinned and keeps
1643
- `force_pull: true` while any image is a bare tag, so an install no longer
1644
- re-resolves a moving tag that other containers share. `inspect` reports each
1645
- service whose image is not digest-pinned. Behaviour for projects with unpinned
1646
- images is unchanged. Not verified on a real DSM.
1647
- - **Breaking: container Apps are DSM-authenticated by default.** A container App
1648
- with an `adminport` is now served through a gateway CGI
1649
- (`/webman/3rdparty/<package>/cgi/gateway.cgi/…`) that runs as the package
1650
- account, checks the DSM session and forwards to the container. Without a valid
1651
- session it answers `401`; unsafe methods need `X-SYNO-TOKEN`; a stopped container
1652
- gives `521` with `Retry-After` (not `503`: DSM would replace its body). New `integration.web.access`: `dsm-users`
1653
- (default), `dsm-admins`, `dsm-groups` (+ `groups`), `public`. No Web Station
1654
- portal is registered unless `public`. The container port must be bound to
1655
- loopback (`127.0.0.1:<adminport>:<port>`): `check` fails with
1656
- `COMPOSE_GATEWAY_PORT_EXPOSED`, and `inspect`/`inspect` report
1657
- `CONTAINER_PORT_EXPOSED` and `CONTAINER_ANONYMOUS_PORTAL`. The gateway is
1658
- compiled Go, so building such an App needs Go and `--arch`, and the SPK is no
1659
- longer `noarch`. Privilege stays `run-as: package` with no root task.
1660
- **Migration:** bind the Compose port to `127.0.0.1`, rebuild with a higher
1661
- `build` and an `--arch`, then upgrade (the old `/<alias>/` portal and bookmarks
1662
- go away); or set `integration.web.access: public` to keep the anonymous
1663
- behaviour. See `website` guide “Container access control”.
1664
- - Add best-effort `revealFile` selection recovery using File Station 1.4.x
1665
- private structures, with feature detection and silent cancellation on absent or
1666
- throwing APIs. One AppLaunch binds its callback receiver; only that receiver's
1667
- model may be selected once. Interaction, navigation away, close, disposal and
1668
- replacement requests cancel observers. Named bounds are 5000 ms total, 100 ms
1669
- checks and 300 ms quiet time, without device latency proof. `requested` still
1670
- does not mean `selected`. Actual effectiveness on DSM 7.4 / File Station 1.4.4
1671
- awaits a rebuilt-SPK device matrix; no device fix is claimed.
1672
-
1673
- - Fix `ctx.host.activateWindow()` to prefer DSM `toFront()`, calling `show()`
1674
- first for hidden/minimized windows; `activate()` remains a compatibility
1675
- fallback. `requestClose()` follows DSM standalone truthiness: falsy values
1676
- (including desktop `undefined`) permit the normal close path, while a truthy
1677
- standalone-main check refuses it. `canClose === false` and host listeners still
1678
- veto. Both methods return `requested`, not completion proof; rebuilt-SPK DSM
1679
- validation remains pending. Source: `7553c24`, `2842634`.
1680
- - Add opt-in package-owned foreground `daemon` to native manifests, with
1681
- generated supervision and lifecycle hooks. Daemon-free packages keep their
1682
- existing lifecycle; no machine-wide service is required.
1683
- - Add per-mount `ctx.host` platform/title adapters, guarded window activation
1684
- and close requests, app/external-URL navigation, resize/reopen subscription
1685
- cleanup, and File Station folder navigation/file reveal. Standalone web uses
1686
- explicit fallbacks; file reveal is not preview. File preview/details/settings
1687
- and host theme/language subscriptions remain unsupported. Final DSM runtime
1688
- behavior and ordinary-user navigation permissions still need NAS validation.
1689
- - Fix SPK inspection to read the build-specific unique window bundle.
1690
-
1691
- ### Known limitations
1692
-
1693
- - **File reveal remains a request:** on DSM 7.4-90069 with File Station
1694
- 1.4.4-2221, one `ctx.host.revealFile()` call was observed opening the correct
1695
- folder without selecting/highlighting the file. A new synthetic fixture retest
1696
- selected the file with both `/home` and `/homes/admin` inputs, once each
1697
- (confidence **high**). The earlier non-selection was not reproduced; its cause
1698
- remains **unknown**. `requested` only means a selection request was issued;
1699
- it does not guarantee selection, preview, existence or ACL access. No runtime
1700
- fix is claimed. Source: V1 device validation and fixture retest, 2026-10-04.
1701
- - Vue templates pin public `@synopackageland/ui@0.4.0`, including app-scoped
1702
- modals and unique mount IDs, with npmjs-only lockfiles and imports. External
1703
- developers do not need an internal registry account.
1704
- - CLI 0.4.0 targets npmjs `latest`; versions 0.1.0–0.3.10 remain available without
1705
- deprecation or unpublishing.
239
+ - `native-go`: a Go backend with a Vue UI (or `--ui plain`).
240
+ - `native-python`, `native-node`, `native-rust`: the same App with a Python,
241
+ Node.js (TypeScript) or Rust backend behind the SDK's Go runtime.
242
+ - `binary`: a static Linux program you build yourself.
243
+ - `compose`: an existing Docker Compose app (`--from compose.yaml`).
244
+ - Existing native products keep their own payload layout with
245
+ `backend: {kind: native}`.
246
+
247
+ ### Capabilities
248
+
249
+ `init --with …` or `add` adds `files`, `jobs`, `login`, `delegate`,
250
+ `root-ops` and `portal`. `delegate` and `root-ops` run through the SDK's
251
+ executor, which an administrator approves once by digest; `check` and
252
+ `build` have the App's executor validate its policy first
253
+ (`EXECUTOR_POLICY_REJECTED`).
254
+
255
+ ### Commands
256
+
257
+ - `init`, `add`: create a project and add capabilities.
258
+ - `check`, `build`: validate the manifest and build the SPK for one or more
259
+ architectures, with the executor checked against the digests shipped in the
260
+ CLI.
261
+ - `inspect`: check any SPK against the DSM 7 package format without
262
+ installing it.
263
+ - `deploy`, `status`, `logs`: install on a NAS and read its state.
264
+ - `dev`, `test`, `images`: run, test and pin images for Compose projects.
265
+ - `skill`: the bundled guidance for AI assistants.
266
+
267
+ `--json` prints one JSON object on stdout; failures carry a stable `code`
268
+ and a `hint`.