@synopackageland/cli 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,802 +1,8 @@
1
1
  # Changelog
2
2
 
3
- ## 0.31.0 (2026-10-25)
3
+ ## 0.32.0 (2026-10-27)
4
4
 
5
- - Pin templates 0.31.x and SDK/App 0.31.0 defaults, including the hardened executor payloads. Rebuild Apps and obtain administrator approval for changed system-access payloads.
6
-
7
- - A bare host name now resolves to `https://host:5001` instead of `http://host:5000`; pass a full URL to keep plain HTTP.
8
- - JSON input duplicate-key detection is now linear-time, so large inputs no longer stall the parser.
9
- - The CLI's own `.env` loading now sets only `DSM_*` and `SYNOPKGLAND_*` variables; set other variables (for example `GOCACHE` or `PATH`) in the shell.
10
- - Harden the DSM curl fallback (headers and form fields go through a private curl config; form text is sent literally), human-readable output (control characters are escaped), `ctl` examples and package versions, the UI directory (must resolve inside the project), and standalone inspect (INFO `package` must be an SDK package id).
11
-
12
- ## 0.30.0 (2026-10-24)
13
-
14
- - Package and forward `identity_gateway.external_access.single_origin`; use SDK 0.30.0 for one-URL Apps, provider-aware DSM menu launches and launch probes.
15
- - Pin templates 0.30.x and its SDK/App 0.30.0 defaults; keep immutable executor/v0.27.0.
16
-
17
- ## 0.29.0 (2026-10-24)
18
-
19
- - Depend on templates `~0.29.0` with SDK/App 0.29.0 and UI 0.14.10. SDK 0.29.0 reuses executor/v0.27.0 through executor manifest format 1; no executor digests change.
20
- - Package `identity_gateway.external_access` (`connect: optional | none`, `origin_change: none | confirm`) for SDK 0.29.0 or later gateways; check refuses it for older SDKs and inspect verifies the packaged external declaration and `settings.cgi` gateway entry.
21
-
22
- ## 0.28.0 (2026-10-24)
23
-
24
- - Depend on templates `~0.28.0` with SDK/App 0.28.0 and UI 0.14.10. SDK 0.28.0 reuses executor/v0.27.0 through executor manifest format 1; no executor digests change.
25
- - Default the Compose startup budget to 900 seconds and document slow-NAS readiness; the SDK owns health timing and the Starting status. No per-App option is added.
26
- - Keep UI CGI scripts executable in built SPKs, and let external OIDC resume tokens reach only the fixed DSM CGI.
27
- - The release gate checks SDK 0.27.1+ executor builds against the SDK executor manifest instead of requiring a frozen legacy table entry.
28
-
29
- ## 0.27.1 (2026-10-23)
30
-
31
- - Read executor release mappings, toolchain and six payload pins from SDK executor manifest format 1. Accept compatible SDK minors independently of CLI versions; keep the frozen 0.26.0/0.27.0 fallback. Remove the CLI digest-recording script.
32
-
33
- ## 0.27.0 (2026-10-23)
34
-
35
- - Depend on templates `~0.27.0` with SDK/App 0.27.0 and UI 0.14.9. Templates patches can ship independently and resolve during npm installation/update; runtime uses the installed templates without network fetching. SDK minor upgrades still require a CLI release for executor mappings/digests and `SDK_UNSUPPORTED` checks. New scaffolds use browser same-origin DSM file operations and the idle-storage fixes.
36
- - Resolve SDK 0.27.0 to the new executor/v0.27.0 and ship all six verified executor/lifecycle digests. Preserve the previous 0.26.0 inventory and exact-version refusal for unrecorded patches. Protected Apps must rebuild and follow normal administrator approval.
37
- - Keep Go 1.26.10 and the existing runtime/toolchain requirements. Rust crate publication and NAS/App upgrade certification are outside this release.
38
-
39
- ## 0.26.0 (2026-10-22)
40
-
41
- - Support SDK/App 0.26.x and pin templates 0.26.0, retaining UI 0.14.3 and executor/v0.24.0 with unchanged protected payload digests.
42
- - Add optional bounded `daemon.restart` crash recovery for native Apps; require SDK 0.26.0 or later, validate the policy and record it for inspection. Initial startup and intentional stops retain their existing behavior.
43
-
44
- - Add `ui: false` for native Apps with `identity_gateway` and no SDK window. The main menu gets one url launcher to `sso/open.cgi` (new tab) and Package Center's Open goes to the same entry through INFO `adminurl`, as for Compose Apps. check refuses it without a gateway, with `console` or `portal`, or with capabilities whose approval or sign-in needs the SDK window (`STANDALONE_UI_NO_WINDOW_INVALID`); inspect verifies the single launcher and `adminurl`. Apps with `ui.dir` build unchanged.
45
-
46
- ## 0.25.2 (2026-10-22)
47
-
48
- - Use templates 0.25.1 with the native Rust lockfile pinned to the public SDK 0.25.0 commit. SDK/App support and executor/lifecycle digests are unchanged.
49
- - Update SDK download and Go build diagnostics for anonymous access to the public repository.
50
-
51
- ## 0.25.1 (2026-10-22)
52
-
53
- - Fix upgrades that first add a `daemon`: the generated `preupgrade` ran the old payload's missing `daemon/control` and DSM rolled back with error 4541. The hook now exits 0 when the outgoing payload has no `daemon/` (plain, executor and delegated-worker variants); inspect still accepts the released unguarded hook.
54
- - Add `daemon.node` (`package: Node.js_v18|v20|v22`, optional `min_version`): the daemon's `exec` is a JavaScript entry run by the Package Center Node.js package through a generated launcher, with INFO `install_dep_packages`. The SPK bundles no Node. See docs/daemon.md for availability and dependency behaviour.
55
- - Add `daemon.ingress` (`path`, optional `timeout`, default 3600 s): a same-origin path in DSM's nginx through web-config `nginx-static-config` `type: dsm`, proxied unbuffered to `/tmp/synopkgland-ingress-<package>.sock`, with nginx reloads in INFO. check requires os_min_ver 7.2-64570 or later (`STANDALONE_DAEMON_INGRESS_MIN_DSM`); inspect verifies the location, resource, INFO and daemon environment.
56
-
57
- ## 0.25.0 (2026-10-22)
58
-
59
- - Add the opt-in `console` declaration for native Apps with `identity_gateway`: a native UI URL launcher and an administrator-only DSM console, selected by Package Center Open. Validate both entries and refuse incompatible launch declarations.
60
- - Fix installation-settings inspection when native identity-gateway diagnostics precede the settings hook, retaining full lifecycle validation.
61
- - Document punctuation rules for owned UI copy in the development skill.
62
- - Support SDK/App 0.25.0 and templates 0.25.0, with UI 0.14.3 in Vue scaffolds. Reuse executor/v0.24.0 with unchanged protected executor/lifecycle payloads and Go 1.26.10. OIDC browser cold-login continuation requires SDK 0.25.0. No NAS acceptance is claimed.
63
-
64
- ## 0.24.1 (2026-10-22)
65
-
66
- - Build SDK/App 0.24.1 with the verified immutable executor/v0.24.0 release and its exact executor/lifecycle digests; retain SDK/App 0.24.0 support. SDK 0.24.1 adds AppShell brand navigation to the first page and supports UI 0.14.3.
67
- - Keep templates 0.24.0 and Go 1.26.10. Executor and lifecycle payloads remain unchanged.
68
-
69
- ## 0.24.0 (2026-10-22)
70
-
71
- - Pin Go 1.26.10 for every SDK runtime build, addressing crypto/x509 GO-2026-6809. Ship the matching executor/lifecycle digest inventory for SDK 0.24.0; older release records are preserved in their release history.
72
- - Include the native HTTPS prestart port-release wait and diagnostics, and SDK/App UI 0.14 compatibility already merged in this release line.
73
- - Use templates 0.24.0 with UI 0.14.0 and the supported DSM Node.js_v22 dependency. Update build/test environments to Node.js 24.22.1 LTS.
74
- - Known limitations: rebuilding and upgrading executor Apps changes their protected payload digests and requires administrator reapproval of system access. No NAS acceptance is claimed.
75
-
76
- ## 0.23.2 (2026-10-22)
77
-
78
- - Build SDK/App 0.23.2 with its verified immutable executor/v0.20.0 digests; retain support for SDK/App 0.23.0 and 0.23.1. SDK 0.23.2 adds compatibility with UI 0.14.0.
79
- - Keep templates 0.23.0 and its existing scaffold pins. Executor and lifecycle payloads remain unchanged.
80
-
81
- ## 0.23.1 (2026-10-22)
82
-
83
- - Build SDK/App 0.23.1 with its verified immutable executor/v0.20.0 digests; retain support for SDK/App 0.23.0. This enables the SDK HTTP transfer error-status fix without changing executor or lifecycle payloads.
84
- - Include the native HTTPS shutdown/start port-release fix and recognize its exact diagnostic prefix when inspecting Portal and background-job lifecycle scripts. Keep templates 0.23.0 and the existing UI pin.
85
-
86
- ## 0.23.0 (2026-10-21)
87
-
88
- - Validate the explicit OIDC `browser_redirect` contract before packaging and pin templates/SDK 0.23.0.
89
- - Reuse executor/v0.20.0 with unchanged executor and lifecycle payloads.
90
-
91
- ## 0.22.0 (2026-10-21)
92
-
93
- - Support SDK/App 0.22.x and pin templates 0.22.0 with published UI 0.12.5.
94
- - Register actionable diagnostics for invalid, unsupported or unbuildable native installation settings.
95
- - Validate provider-only public upstream access, host UDP listeners, fixed launch paths and native installation settings.
96
- - Reuse immutable executor/v0.20.0 with exact SDK-version digest checks.
97
-
98
- ## 0.21.1 (2026-10-21)
99
-
100
- - Resolve SDK/App 0.21.1 to the verified immutable executor/v0.20.0 release while keeping exact SDK-version digest checks.
101
- - Pin templates 0.21.1 with SDK/App 0.21.1 and UI 0.13.1 for new projects.
102
-
103
- ## 0.21.0 (2026-10-21)
104
-
105
- - Validate and preserve `compose.devices.gpu` in check, build and inspect for optional Intel render-node access and software fallback.
106
- - Pin SDK/templates 0.21.0 and reuse immutable executor/v0.20.0 with verified unchanged executor/lifecycle digests.
107
-
108
- ## 0.20.0 (2026-10-21)
109
-
110
- - Align CLI/templates and the supported SDK minor line at 0.20; retain published UI 0.12.5.
111
- - Compare upgrade approval requirements with `build/inspect --baseline`.
112
- - Package native Compose shared-folder mounts with `select: install` and pass host health readiness to the daemon.
113
- - Pin the 0.20.0 executor/lifecycle digest inventory and refresh canonical approval fixtures. Rebuilt executor Apps require administrator reapproval after upgrade.
114
-
115
- ## 0.19.1 (2026-10-20)
116
-
117
- - Record SDK 0.19.1 executor and lifecycle digests for the executor fix that accepts `deadline_ms` on delegated `webapi.call`; SDK 0.19.0 digests remain. Executor Apps rebuilt with SDK 0.19.1 require one administrator reapproval after upgrading.
118
- - Compare upgrade approval requirements with a baseline SPK: `build --baseline` and `inspect --baseline` report root program digests, policy differences, task template changes and executor-only handoff eligibility.
119
- - Retain templates 0.19.0 (SDK 0.19.0) and UI 0.12.5.
120
-
121
- ## 0.19.0 (2026-10-20)
122
-
123
- - Align the CLI SDK minor line and templates at 0.19.0; retain UI 0.12.5.
124
- - Support native DSM OIDC login without a browser bridge for compatible clients.
125
- - Refresh canonical executor setup fixtures and all three executor/lifecycle architecture pins for SDK 0.19.0. Rebuilt executor Apps require one administrator reapproval after upgrading.
126
-
127
- ## 0.18.0 (2026-10-20)
128
-
129
- - Support the SDK 0.18.x minor line and pin templates 0.18.0 with SDK/App 0.18.0 and UI 0.12.5.
130
- - Record SDK 0.18.0 executor and lifecycle digests and refresh executor setup fixtures.
131
- - Add the native OIDC browser bridge and App launch resume flow.
132
- - Default upstream forks to no ctl integration and document the fixed ctl coverage checklist.
133
- - Declare optional native-client passthrough capabilities.
134
-
135
- ## 0.17.8 (2026-10-20)
136
-
137
- - Update SDK 0.17.7 executor and lifecycle digests for the administrator-role fix.
138
-
139
- ## 0.17.7 (2026-10-20)
140
-
141
- - Inspect Compose Apps with custom icons and localized titles using their archived build inputs, preserving every icon size and strict launcher validation. Preserve declared daemon uninstall hooks when checking fixed lifecycle scripts.
142
-
143
- ## 0.17.6 (2026-10-20)
144
-
145
- - Inspect sharedFolder, Portal and install-selected Apps by reconstructing fixed lifecycle scripts from their complete inputs, including native HTTPS ingress and App hooks.
146
-
147
- ## 0.17.5 (2026-10-20)
148
-
149
- - Run native HTTPS preparation inside the SDK lifecycle steps before App hooks, keeping generated hook wrappers inspectable.
150
-
151
- - Preserve older SDK gateway/controller JSON and supported timeout limits so their direct-connection fallback remains buildable; reject expanded budgets that require SDK 0.17.5.
152
-
153
- - Keep Compose named volumes without adding executor/root permissions; warn before uninstall and report their exact undeleted names in nine native locales when delete-data is selected. Clear package-owned data/config/home contents only on explicit uninstall, preserving data on upgrades, stops and keep.
154
-
155
- - Default supported gateway Apps to a DSM-managed HTTPS origin using native package resources and a private authenticated gateway socket. Preserve direct connections for older SDK/DSM targets with an explanation before opening them.
156
- - Prepare private connection credentials as the package account after DSM creates package state, and reject modified gateway proxy settings during SPK inspection.
157
- - Apply `ctl --timeout` to discovery, JSON requests, polling and transfer inactivity without replaying uncertain writes; use a 62-second default transport allowance.
158
- - Reuse `daemon.timeouts` for native, Compose and background lifecycle work, including declarations that need no daemon executable. Allow lifecycle and App purge budgets up to 3600 seconds while retaining the Compose startup cap.
159
- - Generate matching gateway, supervisor and approved executor budgets, preserve them through SPK inspection, and report pending uninstall cleanup within bounded TERM/reap reserve.
160
-
161
- - Pin templates 0.17.8 and SDK/App 0.17.5, including UI 0.12.4 full-page layout. Updated executor and lifecycle payloads require administrator reapproval after upgrading rebuilt Apps.
162
-
163
- ## 0.17.4 (2026-10-19)
164
-
165
- - Accept `compose.startup_timeout_seconds` (default 120, integer 2–900), use the SDK plan's value for daemon start, and deliver the same budget to container bootstrap adapters through the SDK-generated environment. Reject a planner that cannot honor an explicitly declared budget. Keep ingress closed until readiness succeeds.
166
-
167
- - Show actionable image-pull diagnosis in nine locales and direct users to Package Center Repair when a package starts with stale container assets.
168
- - Pin templates 0.17.6 and SDK/App 0.17.4; keep UI at 0.12.3.
169
-
170
- ## 0.17.3 (2026-10-19)
171
-
172
- - Pin templates 0.17.5, SDK/App 0.17.3 and UI 0.12.3 for generated Apps.
173
- - Record SDK 0.17.3 executor and lifecycle digests; all six payloads are unchanged from 0.17.1.
174
- - Keep the “User password” wording in all nine locales and package the generated native SDK version identifiers.
175
-
176
- ## 0.17.2 (2026-10-19)
177
-
178
- - Pin templates with matching frontend and native SDK versions.
179
- - Record the verified SDK 0.17.1 executor and lifecycle payload digests.
180
- - Rename password labels and approval instructions to “User password” across all nine locales.
181
-
182
- ## 0.17.1 (2026-10-19)
183
-
184
- - Remove environment-specific release notes and source comments from distributed content, including generated DSM window scripts.
185
- - Prepare npm releases from a clean package directory without local archive references.
186
-
187
- ## 0.17.0 (2026-10-19)
188
-
189
- - Pin SDK/App 0.17.0 and templates 0.17.1, with UI 0.12.1 in Vue scaffolds; package the executor fixes for directory users and request batches.
190
- - Fetch SynoToken early in standalone and Portal windows so the first API request can start sooner.
191
- - Allow Portal without an executor from DSM 7.2-64570. Request DSM nginx reload through INFO during install, upgrade and uninstall; DSM 7.1 still needs an executor for registration.
192
-
193
- - Expose ctx.batch in generated SDK windows, sharing the existing request guards and token recovery. Keep single-call retries unchanged; never replay a batch after partial execution.
194
-
195
- ## 0.16.0 (2026-10-19)
196
-
197
- - Pin SDK/templates 0.16.0; generated Shared Folder flows use the shared SDK readback and settings components.
198
- - Add the upstream-fork recipe/init/check workflow, Container Manager preflight and architecture-matched storage initializer packaging while preserving official images.
199
- - Remove internal deployment details from published content and scan actual npm tarballs before release.
200
-
201
- ## 0.15.4 (2026-10-18)
202
-
203
- - Fix Compose import and undeclared SDK defaults after CLI-only patch releases. Resolve the SDK from the exactly pinned templates' Go module instead of assuming the CLI and SDK share a patch version; daemon-control defaults use the same source.
204
- - Preserve explicit Compose and identity-gateway SDK declarations. Add subprocess regressions with differing CLI/SDK versions and no SDK source override, plus release smoke coverage for real Compose imports and headless manifests.
205
- - Keep templates/SDK pinned to 0.15.2 and every executor/lifecycle digest entry unchanged. Upgrade from CLI 0.15.3 to 0.15.4 to restore `init -t compose --from`; SDK/helper bytes do not change and no new administrator reapproval requirement is introduced.
206
-
207
- 繁中摘要:修正 CLI 與 SDK patch 版本不同時,Compose 匯入/未宣告 SDK 的預設路徑下載不存在 SDK tag 的問題;改由固定 templates 的 Go module 讀取 SDK pin,daemon-control 預設值亦同步。保留明確 SDK 宣告的優先順序,新增無 source override 的版本拆分回歸測試與正式產物 smoke。templates/SDK 保持 0.15.2,全部 executor/lifecycle digest 不變;請由 CLI 0.15.3 升級至 0.15.4,不新增管理員重新核准要求。
208
-
209
- ## 0.15.3 (2026-10-18)
210
-
211
- - Pin templates and SDK fixtures/examples to 0.15.2, including UI 0.12.0 scaffolds, concise UI copy/tokens and delegate DTO contract checks.
212
- - Add the SDK 0.15.2 executor/lifecycle digest entry using Go 1.26.9; retain every earlier entry unchanged. All six digests match 0.15.1, so upgrading from 0.15.1 adds no administrator reapproval requirement. Apps upgrading from 0.15.0 retain the one-time approval introduced in SDK 0.15.1.
213
- - Include CLI-5 shared OFF_GLOSSARY rules and CLI-6 command failure exit codes from CLI 0.15.2. CLI 0.15.2 was already published with templates 0.15.1; use 0.15.3 for the coordinated SDK/templates release.
214
-
215
- 繁中摘要:templates/SDK pins 同步 0.15.2,帶入 UI 0.12.0、精簡文案/token 與 DTO 契約檢查;保留 CLI-5 詞彙規則及 CLI-6 正確退出碼。新增 SDK 0.15.2 digest 表項,六個 digest 均與 0.15.1 相同,保留全部舊 pins;從 0.15.1 升級不新增重新核准要求,從 0.15.0 升級仍需完成一次管理員核准。CLI 0.15.2 已先發佈,因此本次協同發版使用 CLI 0.15.3。
216
-
217
- ## 0.15.2 (2026-10-18)
218
-
219
- - Preserve command-set process exit codes instead of resetting them to 0 after CLI dispatch. `ctl` failures now reach scripts and CI with their documented exit codes; successful commands still exit 0 and uncaught exceptions exit 1.
220
- - Add subprocess regression coverage for `ctl` exit codes 0–8, confirmation before writes, and failures from `check`, `build`, `inspect`, `status` and `test`. Document the existing `ctl` exit-code contract and informational discovery/session results.
221
- - CLI-only patch: templates and SDK fixtures/examples remain pinned to 0.15.1; executor/lifecycle digests are unchanged.
222
-
223
- 繁中摘要:修正 CLI 在子指令完成後把退出碼覆寫成 0 的問題,讓 `ctl` 失敗正確傳遞至腳本與 CI;成功仍為 0,未捕捉例外仍為 1。新增實際子行程測試,涵蓋 `ctl` 的 0–8 退出碼、寫入確認及其他主要指令的失敗;README 記錄既有退出碼與 discovery/session 資訊結果。此次僅更新 CLI,templates/SDK 維持 0.15.1,executor/lifecycle digest 不變。
224
-
225
- ## 0.15.1 (2026-10-18)
226
-
227
- - Require explicit `DSM_E2E_SSH` for e2e tunnels and use `dsm` as the default saved-profile target; set `DSM_E2E_TARGET` to reuse an earlier target name. Anonymize internal NAS fixtures and documentation.
228
- - Pin templates and SDK fixtures/examples to 0.15.1. Add the 0.15.1 executor/lifecycle digest entry and retain the published 0.15.0 pins unchanged.
229
- - Cover package-named control.sh refusal logs and rejected `.executor` / `.policy.json` staging cleanup.
230
- - Executor task definitions change: Apps rebuilt with SDK 0.15.1 require one administrator reapproval after upgrade.
231
-
232
- 繁中摘要:e2e tunnel 必須明確設定 `DSM_E2E_SSH`,profile 預設 target 改為 `dsm`(沿用舊 profile 時設定 `DSM_E2E_TARGET`);清理內部 NAS 範例資訊。templates/SDK pins 同步 0.15.1;新增同版三架構 executor/lifecycle digest,保留 0.15.0 pins。驗證 control.sh 拒絕 log 的套件名與 runtime copies 清除。**task 定義改變:App 使用新 SDK 重建升級後,管理員需重新核准一次。**
233
-
234
- ## 0.15.0 (2026-10-18)
235
-
236
- - Add `synopkgland ctl` host/session management, authenticated API v1 discovery, explicit command trust, typed invocation and fixed job/pagination/NDJSON/upload flows. Commands keep App backend authorization; writes require confirmation.
237
- - Document resource/verb naming recommendations without a verb allowlist or effect inference; explicit effects remain mandatory and binding checks remain unchanged.
238
- - Redact standard JSON Schema writeOnly values in dry-run/confirmation plans, schema errors, results, list/help and caches, including nested arrays/objects and multi-host output; keep outgoing requests unchanged.
239
- - Pin templates and released executor digests to 0.15.0, retaining the same-minor digest selection rule.
240
- - Breaking: remove the legacy Compose route and bridge publish mode. Migrate to schema 2 `dsm-package.yaml` with ordered `compose.files`, `compose.web` container port/health and `identity_gateway.sdk: 0.15.0`; remove old `integration.compose`/`integration.identity_gateway`. `web.host_port` is accepted but ignored. Build/check/inspect share the SDK planner. Rebuild with templates/SDK 0.15.0 and verify install/upgrade; historical `.sc` cleanup requires separate authorization. Changing subnet candidate requires a clean installation, not an automatic network migration.
241
-
242
- 繁中摘要:新增 writeOnly 敏感值遮罩(巢狀、陣列、多主機、計畫、錯誤及快取),實際請求保持原值。新增 `synopkgland ctl`,從已安裝 App 的 authenticated「Syno Package Land API 描述」v1 發現並執行明確 commands,支援多主機與固定任務流程。**不相容變更**:移除舊 Compose 打包路線及 publish 模式;改用 schema 2 `compose`/`identity_gateway`,固定 SDK/templates 0.15.0 並重建驗證。gateway 直接連固定 bridge IP 的 container port;`web.host_port` 僅解析相容、會忽略,歷史 `.sc` 與既有子網不自動清除或遷移。
243
-
244
- ## 0.14.1
245
-
246
- - DSM 會改寫 standalone URL,因此沒有 SDK class 的 schema 1 web launcher 使用 url;schema 2 identity_gateway + compose 與原生 new-page 使用 standalone。兩條 Compose 路線共用預設解析。
247
-
248
- ## 0.14.0
249
-
250
- - 原生 App 支援 ui.launch: dsm-window | new-page;new-page 使用 DSM standalone launcher。SDK 原生 App 的內建預設維持 dsm-window;Docker/Web App(含 schema 2 identity_gateway + compose)共用解析邏輯,內建預設改為 new-page,主選單與套件中心皆可開啟既有 gateway/public alias。inspect 驗證 launcher 與 Portal 宣告,完整頁統一設定標題、favicon 與手機 viewport。Web App 省略開啟方式的行為變更,因此採用 0.14.0。
251
-
252
- ## 0.13.1 (2026-10-17)
253
-
254
- - Use templates 0.13.1, SDK 0.13.1 fixtures and the unchanged executor/lifecycle digests on the 0.13 minor line.
255
-
256
- 繁中摘要:模板與 SDK 範例同步至 0.13.1;沿用 0.13.0 的 executor/lifecycle digest,既有 App 不因本次版本更新重核准。
257
-
258
- ## 0.13.0 (2026-10-17)
259
-
260
- - Add the identity gateway for daemon web applications and package-account compose upstreams: OIDC, header and token modes, validated native payload targets and bounded connection policies. UDP connections are handled separately from TCP.
261
- - `account.on_delete` declares application account/session cleanup when a local DSM account disappears; domain accounts support explicit gateway purge. Bundle the `identity-gateway` skill for wrapping and verifying existing open-source applications.
262
- - Generated Apps pin templates/SDK 0.13.0 and UI 0.10.5; record the 0.13.0 executor/lifecycle digests. Skill examples use the released SDK version. This CLI builds SDK 0.13.x; upgrade backend and frontend pins together.
263
-
264
- 繁中摘要:daemon 與 package-account compose App 可宣告 identity gateway,以 DSM 身分透過 OIDC、header 或 token 登入,並限制原生 payload 目標與網路連線(區分 UDP/TCP)。`account.on_delete` 宣告本機 DSM 帳號刪除時的 App 帳號/session 清理;網域帳號支援明確 purge。新增 `identity-gateway` skill 說明既有 OSS App 的包裝及驗證。templates/SDK 同步 0.13.0、UI 0.10.5,更新 executor digest 與範例版本;此 CLI 建置 SDK 0.13.x,後端/前端 pin 應一併升級。
265
-
266
- ## 0.12.2 (2026-10-16)
267
-
268
- - Generated Apps use templates 0.12.2, SDK 0.12.2 and UI 0.10.3. SDK 0.12.2 types `ctx.launchParams` and `ctx.onLaunch` and adds `followLaunchRoute`, `launchRoute` and `dsmLaunchURL`.
269
- - Pin the SDK 0.12.2 executor and lifecycle digests, identical to 0.12.1 for all three architectures.
270
- - The DSM window forwards launch parameters to the App. `ctx.launchParams` holds the App's own string values from the launch that opened the window, from a desktop URL `?launchApp=SYNO.SDS.<package>.Instance&launchParam=<url-encoded key=value&...>` or `SYNO.SDS.AppLaunch(id, {key: value})`; `ctx.onLaunch(fn)` runs fn with those of each later launch of the open window. DSM's own keys, non-string values, keys outside `[A-Za-z][A-Za-z0-9_.-]{0,63}`, values over 2048 characters or with control characters, and keys past 32 are left out.
271
- - `ctx.reopen` callbacks now run when DSM launches the already open window again (main menu, desktop URL or AppLaunch); DSM calls the window's onRequest then, never onOpen again.
272
-
273
- 繁中摘要:新專案使用 templates 0.12.2、SDK 0.12.2 與 UI 0.10.3,並加入 SDK 0.12.2 的 executor digest(與 0.12.1 相同)。DSM 視窗把啟動參數轉給 App:`ctx.launchParams` 是開啟視窗那次的 App 字串參數(桌面 URL `launchParam` 或 `SYNO.SDS.AppLaunch`),`ctx.onLaunch(fn)` 在已開視窗再次被帶參數啟動時呼叫;DSM 自有欄位、非字串、不合格的 key、超過 2048 字元或含控制字元的值與第 32 個之後的 key 會被略過。已開視窗再次啟動時 `ctx.reopen` 現在會觸發(DSM 呼叫 onRequest,不會再呼叫 onOpen)。
274
-
275
- ## 0.12.1 (2026-10-16)
276
-
277
- - Preserve optional `raw_max_bytes` in WebAPI profiles and administrator policy digests. Reject invalid limits and require raw-capable SDK 0.12.1; zero/omitted profiles stay JSON-only. Generated approval details receive the byte limit.
278
- - Pin the SDK 0.12.1 three-architecture executor and lifecycle digests. Templates remain 0.12.0; no template change is needed for an optional consumer-declared profile.
279
-
280
- 繁中摘要:WebAPI profile 支援 raw_max_bytes 明確啟用原始回應與大小上限;驗證及核准 digest 保留此設定,需搭配 SDK 0.12.1。更新 executor digest,templates 維持 0.12.0。
281
-
282
- ## 0.12.0 (2026-10-16)
283
-
284
- - Generated Apps use templates 0.12.0, SDK 0.12.0 and UI 0.10.1. Vue pages and sections stack at the UI section gap, and SDK dialogs size to their content.
285
- - `metadata.screenshots` accepts up to 5 `https://` URLs, matching the catalog; `check` refuses more, or an `http://` URL, with `STANDALONE_TEXT_INVALID`.
286
- - The executor digest table records SDK 0.12.0, whose executor and lifecycle digests equal 0.11.0's. This CLI builds SDK 0.12.x executors; upgrade backend and frontend pins to 0.12.0 together.
287
- - 繁中:產生的 App 使用 templates 0.12.0、SDK 0.12.0 與 UI 0.10.1;Vue 頁面與區塊以 UI section gap 排列,SDK 對話框依內容決定寬度。`metadata.screenshots` 最多 5 個 `https://` URL,與 catalog 一致,超過或使用 `http://` 時 `check` 回報 `STANDALONE_TEXT_INVALID`。digest 表記錄 SDK 0.12.0(與 0.11.0 相同);此 CLI 建置 SDK 0.12.x executor,後端/前端 pin 需一併升級至 0.12.0。
288
-
289
- ## 0.11.0 (2026-10-16)
290
-
291
- - Add capabilities.webapi named profiles with purpose and exact API/method/version lists, pinned in the administrator-approved executor policy. Build, inspect, lifecycle, bridge services and consent details support WebAPI-only Apps without granting file/root operations.
292
- - Require SDK 0.11.0 for WebAPI profiles and record the three-architecture executor/lifecycle digests. The SDK and templates now share the 0.11 minor line; upgrade backend/frontend pins together.
293
- - 繁中:新增具名 WebAPI profile,宣告用途與 API/method/version 清單並納入管理員核准;建置、檢查、生命週期與 bridge 支援僅 WebAPI 的 App。使用此功能需 SDK 0.11.0,後端/前端 pin 一併升級至 0.11。
294
-
295
- ## 0.10.12 (2026-10-15)
296
-
297
- - Generated Apps use templates 0.10.13, SDK 0.10.12 and UI 0.9.10, including PageInfo, responsive page headers and confirmation before removing a Portal page's own entry.
298
- - Executor and lifecycle digests are unchanged from SDK 0.10.11 for all three architectures.
299
-
300
- 繁中摘要:更新 templates 至 0.10.13、SDK 至 0.10.12、UI 至 0.9.10;帶入頁面附註、標題換行與入口移除確認。三架構 executor/lifecycle digest 不變。
301
-
302
- ## 0.10.11 (2026-10-15)
303
-
304
- - Bridge App builds with a manifest minimum below DSM 7.2 inject executor portal mode so Rust, Python and Node Apps can use the fixed entry enable/remove routes on DSM 7.1 and 7.4.
305
- - Pin templates 0.10.12 and SDK 0.10.11, including the three updated executor digests. Generated Vue Apps use UI 0.9.9.
306
-
307
- 繁中摘要:manifest 最低 DSM 低於 7.2 時,bridge App 建置帶入 executor portal mode,提供 DSM 7.1/7.4 的入口啟用與移除;更新 templates 0.10.12、SDK 0.10.11 及三架構 executor digest,UI 保持 0.9.9。
308
-
309
- ## 0.10.10 (2026-10-15)
310
-
311
- - Uses SDK 0.10.10, templates 0.10.11 and UI 0.9.9. `init` pins these versions for the generated project, and the executor digest table pins SDK 0.10.10 for x86_64, aarch64 and armv7.
312
- - The shared DSM window wrapper waits for the SDK session helper before backend and DSM WebAPI calls. A portal window without a desktop SynoToken obtains the current token through a shared same-origin request; cookie, token, principal and original client-address validation remain enforced.
313
- - Every Go child process merges `github.com/synopackageland/*` into its effective `GOPRIVATE`, `GONOPROXY` and `GONOSUMDB`, preserving environment and `go env -w` settings. Git credentials are needed to fetch the private SDK. Direct `go get` commands supply their own GOPRIVATE setting.
314
- - SDK approval components explain the requested system privileges with a single consent card, expandable details and inline password errors. `ExecutorApproval`, `ExecutorNotice` and `ExecutorSettings` take `purpose`; background-save settings use one save-folder row per destination key. See the SDK CHANGELOG for translation-key and slot changes.
315
- - App pins: `@synopackageland/cli` 0.10.10 with its lockfile; SDK 0.10.10 for Go, npm, Cargo and `backend.sdk`; UI 0.9.9. Rebuild the App. An unchanged policy and a matching root-owned INFO anchor preserve an existing approval; policy or capability changes require administrator approval.
316
-
317
- 繁中摘要:使用 SDK 0.10.10、templates 0.10.11、UI 0.9.9 與三架構新 executor digest。portal 缺少 token 時,視窗 wrapper 先等待共用 SDK helper 取得 token,再呼叫後端與 DSM WebAPI,維持驗證。所有 Go 子行程自動合併 private namespace 並保留既有設定。核准畫面使用 consent 卡片,元件可傳入 `purpose`;背景存檔設定每個 key 只有一列存放資料夾。App 更新 exact pin 與 lock 後重建。
318
-
319
- ## 0.10.9 (2026-10-15)
320
-
321
- Builds with SDK 0.10.9 and templates 0.10.10: `init` writes SDK v0.10.9,
322
- `@synopackageland/sdk` 0.10.9 and `@synopackageland/ui` 0.9.8, and the root-ops
323
- template's `ExecutorGate.vue` passes `preload`. The executor digest table adds
324
- SDK 0.10.8 and 0.10.9 (equal to each other; the executor changed in 0.10.8).
325
- An executor upgrade keeps the existing approval when the policy is unchanged
326
- and the new executor matches its root-owned INFO anchor. A policy or capability
327
- change requires an administrator's approval again.
328
- The CLI still needs SDK 0.10.5 or later: an executor without `tasks` (before
329
- 0.10.8) receives the window's full readback. Without an executor setup,
330
- SDK 0.10.9's `ExecutorApproval` shows the pages alone, with or without `preload`.
331
-
332
- To upgrade an App: devDependency `@synopackageland/cli` 0.10.9 (exact) with
333
- its lockfile; an App that depends on the SDK directly moves to 0.10.9 (Go
334
- `require github.com/synopackageland/syno-pkg-land-sdk v0.10.9` with go.sum,
335
- `@synopackageland/sdk` and `@synopackageland/app` 0.10.9, Cargo tag
336
- `v0.10.9`, `backend.sdk: 0.10.9`); then rebuild and upgrade. An unchanged
337
- policy and matching root-owned INFO anchor preserve the existing approval.
338
-
339
- 繁中摘要:搭配 SDK 0.10.9、templates 0.10.10(UI 0.9.8)。digest 表新增 SDK 0.10.8/0.10.9
340
- (兩者相同,executor 在 0.10.8 改變)。policy 不變且新 executor 符合 root-owned INFO anchor 時,
341
- 升級沿用原核准;policy/能力改變才需管理員重新核准。SDK 下限維持 0.10.5(沒有 `tasks` 的舊 executor 完整核對)。沒有 executor setup 時,SDK 0.10.9 只顯示頁面。
342
- App 要改:`@synopackageland/cli` 0.10.9+lock;直接依賴 SDK 的 App 升到 0.10.9。
343
-
344
- - The DSM window shows an administrator the App without reading Task
345
- Scheduler and EventScheduler when the pinned, running executor's status
346
- says its own readback of the approval tasks is `ready` (`tasks`, SDK
347
- 0.10.8 or later). That readback runs at the executor's start and
348
- every 10 minutes; a task another administrator changes after it is seen at
349
- the next one, up to 15 minutes later, instead of when the window opens.
350
- Approval, the control task's start, stop and purge, and a scope selection
351
- still read both tasks back first. An executor without `tasks`, or with a
352
- readback that is not `ready`, is read back by the window as before, and
353
- the first status request now also serves its health check.
354
- 繁中:管理員開啟視窗時,若 executor 回報自己的排程核對為 ready,就不再呼叫兩個
355
- 排程 API;核准、start/stop/purge 與範圍選擇仍先完整核對。舊 executor 照舊核對。
356
-
357
- ## 0.10.8 (2026-10-15)
358
-
359
- Builds with SDK 0.10.7 and templates 0.10.8, as 0.10.7 did: `init` still
360
- writes SDK v0.10.7, `@synopackageland/sdk` 0.10.7 and `@synopackageland/ui`
361
- 0.9.8, and the executor digest table is unchanged, so an App rebuilt on it
362
- keeps its approval when the policy is unchanged and the executor matches its
363
- root-owned INFO anchor. A policy or capability change requires approval again. Only the DSM
364
- window's `ctx.call` changes. The CLI still needs SDK 0.10.5 or later: the
365
- SDK's Gate, `useLifecycleRetry`, `appLifecycle` and `ExecutorApproval` already
366
- read an `APP_STOPPED` that arrives at once and check again; SDK 0.10.7 changed
367
- only its protocol README's wording, and nothing in the CLI depends on it.
368
-
369
- 繁中摘要:搭配 SDK 0.10.7、templates 0.10.8(UI 0.9.8),digest 不變;SDK 下限維持 0.10.5
370
- (SDK 的 Gate/`useLifecycleRetry`/`appLifecycle`/`ExecutorApproval` 本來就處理立即回的
371
- `APP_STOPPED`)。只改 DSM window 的 `ctx.call`。
372
-
373
- - `ctx.call` rejects `APP_STOPPED` at once: a stopped package waits for the
374
- user to start it in Package Center, so the SDK's stopped copy shows within a
375
- page's next read instead of 30 s later (Download Hub 29.3 s; Reclaim
376
- showed its old content or loading). `APP_STARTING` and
377
- `EXECUTOR_STARTING` repeat for 4 s of waiting (0.5, 1, 2, 0.5 s), so a short
378
- start still shows the page nothing and the SDK's starting copy follows a
379
- longer one within its own 4 s recheck. `executor.control`, which the window's
380
- approval starts the executor with, still repeats a start for 30 s; the SDK's
381
- status reads are still not repeated.
382
- 繁中:套件停止時 `ctx.call` 立即回 `APP_STOPPED`;啟動中最多重試 4 s(`executor.control` 維持 30 s)。
383
-
384
- ### Behavior changes
385
-
386
- - An App rebuilt with CLI 0.10.8 shows a stopped package's copy within its
387
- page's next read: `ctx.call` rejects `APP_STOPPED` at once instead of after
388
- about 30 s, and repeats `APP_STARTING` and `EXECUTOR_STARTING` for at most
389
- about 4 s. The App's code needs no change; only its devDependency
390
- `@synopackageland/cli` 0.10.8 and its lockfile, then a rebuild.
391
- - An e2e test that waits only for the Gate's stopped title
392
- (`authorization.stopped.title`) can miss it: `ExecutorApproval` now takes
393
- over first and shows its own stopped copy. Accept either of the SDK's stopped
394
- copies.
395
- 繁中:App 升到 CLI 0.10.8 重新 build 後,套件停止時頁面立即收到 `APP_STOPPED`(不再等 30 s),
396
- 啟動中最多重試約 4 s;App 不需改程式。E2E 若只等 Gate 的停止標題,應接受 SDK 的任一停止文案。
397
-
398
- ## 0.10.7 (2026-10-15)
399
-
400
- Builds with SDK 0.10.7 and templates 0.10.8: `init` writes SDK v0.10.7 (Go
401
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.7 and
402
- `@synopackageland/ui` 0.9.8. The executor digest table adds SDK 0.10.7, whose
403
- executor and lifecycle digests equal 0.10.6, so an App rebuilt on it keeps its
404
- approval unless its executor or policy changes. The CLI still needs SDK
405
- 0.10.5 or later: `ctx.portal.lastStatus()` is new and optional, SDK 0.10.7's
406
- `PortalSettings` keeps its placeholder rows without it, and older SDKs do not
407
- read it; nothing in the CLI depends on SDK 0.10.7. An App gets the settings
408
- page without placeholder movement only with both SDK 0.10.7 and a DSM window
409
- built by CLI 0.10.7.
410
-
411
- 繁中摘要:搭配 SDK 0.10.7、templates 0.10.8(UI 0.9.8);digest 與 0.10.6 相同;SDK 下限維持
412
- 0.10.5(`ctx.portal.lastStatus()` 是選用的新增,舊 SDK 不讀、SDK 0.10.7 沒有它就畫佔位列)。
413
- e2e 新增 `openUntilLoaded`(`signIn` 遇 `net::ERR_ABORTED` 最多重開約 30 s)、遮蔽 socket.io `sid=`。
414
-
415
- - `signIn` and `dsmLoginSetup` keep reopening DSM's login page while the
416
- navigation fails with `net::ERR_ABORTED`, for up to about 30 s, after a saved
417
- profile is refused (error 119); they then fail with a clear error. Any other
418
- error still fails at once. The helper is exported as
419
- `openUntilLoaded(app, path, {timeoutMs, pauseMs})`.
420
- - e2e output and report masking also masks the `sid=` query parameter of DSM's
421
- socket.io URLs, which carries the connection id.
422
- - `ctx.portal` reads the portal status when the DSM window opens and keeps
423
- the newest one it read (`lastStatus()`); a `status()` asked for while a read
424
- runs shares it. The SDK's `PortalSettings` starts from it, so a settings
425
- page no longer moves when its placeholder rows become the real section
426
- (Reclaim: 100 px).
427
-
428
- ## 0.10.6 (2026-10-15)
429
-
430
- Builds with SDK 0.10.6 and templates 0.10.7: `init` writes SDK v0.10.6 (Go
431
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.6 and
432
- `@synopackageland/ui` 0.9.8, whose `CellText` takes `wrap`. Templates 0.10.6
433
- was tagged but not published (a failing delegate frontend test); 0.10.7 is the
434
- same templates with that test fixed. The executor digest table adds SDK 0.10.6,
435
- whose executor and lifecycle digests equal 0.10.5, so an App rebuilt on it keeps
436
- its approval unless its own root programs change. The CLI still needs SDK 0.10.5
437
- or later: SDK 0.10.5's panels already read `executor_starting` as a starting
438
- package and check again, and nothing in the CLI depends on SDK 0.10.6.
439
-
440
- 繁中摘要:搭配 SDK 0.10.6、templates 0.10.7(UI 0.9.8);templates 0.10.6 有 tag 但未發佈;
441
- digest 與 0.10.5 相同;SDK 下限維持 0.10.5。e2e 新增 `openPath`、`handleDialogs`、80/443 通道。
442
-
443
- - `signIn`, `openDesktop` and `openPortal` open their page once more when
444
- the first navigation fails with `net::ERR_ABORTED`, which about one run in
445
- ten met as DSM's desktop navigated on its own; any other
446
- failure, or a second abort, still fails. The helper is exported as
447
- `openPath(app, path)`.
448
- - The DSM window's `status()` answers a pinned executor that reports
449
- `state: "starting"` (its `root.start` step) at once, blocked with code
450
- `executor_starting` and no approval offered, instead of waiting up to 75 s
451
- for the step. The SDK's panels read that code as a starting package and
452
- check again on their own, as they do `APP_STARTING` since CLI 0.10.5.
453
- Approving and `control("start")` still wait for the step.
454
- - `openApp(app, appId, timeout, browser)` and `openPortal(app, path, browser)`
455
- take the test's `browser` fixture and accept native alert and confirm dialogs
456
- through its `onDialog`, recording their text (`handleDialogs(browser)` returns
457
- the list). DSM's `alert("You are not authorized to use this service.")` no
458
- longer fails the test as an unhandled dialog. Without `browser` nothing
459
- changes.
460
- - `openPortal` registers the session's cookies as e2e secrets too (a portal
461
- page may have no SynoToken, which is no failure); only `signIn` and
462
- `openDesktop` did.
463
- - `signIn` accepts a restored profile only when the desktop holds a SynoToken
464
- and DSM's `/webman/login.cgi` still accepts the session (`dsmSessionLive`).
465
- A profile with only the `stay_login` cookie, or an expired session (DSM error
466
- 119), now clears the cookies and signs in again with the account file's
467
- password; the desktop alone used to be enough, and the app's CGIs then
468
- answered "authorization unavailable".
469
- - `runWithTunnel` also forwards the NAS's port 80 and 443 when
470
- `DSM_E2E_TUNNEL_HTTP_PORT` and `DSM_E2E_TUNNEL_HTTPS_PORT` name local ports
471
- (portal alias tests), and hands both to the command; the targets default to
472
- 80 and 443 (`DSM_E2E_REMOTE_HTTP_PORT`, `DSM_E2E_REMOTE_HTTPS_PORT`). Without
473
- them the tunnel is unchanged.
474
-
475
- ## 0.10.5 (2026-10-15)
476
-
477
- Builds with SDK 0.10.5 and templates 0.10.5: `init` writes SDK v0.10.5 (Go
478
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.5 and
479
- `@synopackageland/ui` 0.9.7, whose `Space` aligns its items to the start. The
480
- executor digest table adds SDK 0.10.5, whose executor and lifecycle digests
481
- equal 0.10.4, so an App rebuilt on it keeps its approval unless its own root
482
- programs change. New: the `@synopackageland/cli/e2e` module for real-NAS e2e
483
- suites.
484
-
485
- 繁中摘要:搭配 SDK 0.10.5、templates 0.10.5(UI 0.9.7);需要 SDK 0.10.5 以上;
486
- digest 與 0.10.4 相同;新增 `@synopackageland/cli/e2e`。
487
-
488
- - **Incompatible:** the CLI needs SDK 0.10.5 or later. `check` and `build` refuse an App
489
- whose backend requires SDK 0.10.3 or 0.10.4 (`SDK_UNSUPPORTED`, naming 0.10.5); raise
490
- the Go module, Cargo tag or `backend.sdk` and the frontend's `@synopackageland/sdk` to
491
- 0.10.5. The DSM window now answers the SDK's status reads at once while the package is
492
- stopped or starting, and only SDK 0.10.5's panels show that state and check again;
493
- SDK 0.10.4's would show the package as needing approval.
494
-
495
- 繁中摘要:CLI 需要 SDK 0.10.5 以上;App 若仍用 SDK 0.10.3/0.10.4,`check`/`build` 會以
496
- `SDK_UNSUPPORTED` 拒絕。DSM 視窗的 status 讀取不再替 App 重試,只有 SDK 0.10.5 的面板會自行重試。
497
- - `@synopackageland/cli/e2e` (`src/e2e/dsm.ts`): DSM helpers for a package's
498
- real-NAS suite on `e2e` 0.19 and `@e2e-dev/web` 0.14 (optional peers).
499
- `dsmTarget`, `magpieAgent` (magpie GPT-6 Luna at max reasoning effort,
500
- `E2E_LLM_*` override), `dsmLoginSetup` and `signIn` (a mode 600 saved profile,
501
- restored while it still reaches the desktop), `openDesktop`, `openApp`,
502
- `openPortal`, `dismissNotifications` and `runWithTunnel`
503
- (`node dsm.js tunnel -- <command>`, an npm script such as `e2e:nas`). The file
504
- is self-contained, so a package may copy it to `tests/e2e/support/dsm.ts`.
505
- 繁中:新增共用 DSM e2e 模組,App 可改為 import `@synopackageland/cli/e2e`。
506
- - `runWithTunnel` opens the tunnel on a free loopback port unless
507
- `DSM_E2E_TUNNEL_PORT` names one, so parallel runs do not collide on 15000.
508
- - A saved DSM profile is reused by host, not port, and saved atomically, so
509
- concurrent runs never leave it half written.
510
- - `dismissNotifications` waits up to `DSM_E2E_NOTIFICATION_WAIT_MS` (default
511
- 5000) for the cards DSM shows after the desktop (the admin's "Admin account
512
- security" card, about 2 s later on DSM 7.4) and matches DSM 7.4's container.
513
- - `runWithTunnel` masks DSM's session cookies (`id`, `did`, `_SSID`),
514
- `X-SYNO-TOKEN`, `SynoToken` and `_sid` as `***` in the command's stdout and
515
- stderr and in the text files under `DSM_E2E_OUTPUT_DIR` (default `.e2e`), and
516
- makes that directory private (directories 700, files 600). A Playwright call
517
- log in a run error had carried the session cookie into `report.json` and the
518
- run's output; e2e redacts only the secrets its config declares. Saved
519
- sessions keep their cookies. `redactDsmSecrets` and `protectE2eOutput` are
520
- exported for a package with its own runner. Before that, `signIn` and
521
- `openDesktop` (and so `openApp`) register the session's cookies and
522
- SynoToken as e2e secrets in the test's worker (`registerDsmSecrets`), so e2e
523
- itself masks them in a run error, `report.json` and its output; e2e 0.19
524
- keeps that ledger internal, so a later layout without it leaves the second
525
- mask alone.
526
- 繁中:e2e 輸出與 log 遮蔽 DSM cookie/token,輸出目錄改為 700/600。
527
- - `ctx.call` no longer repeats the SDK's status reads (`delegate.status`,
528
- `authorization.status`, `executor.status`) while the package is stopped or
529
- starting: they reject with the code at once, and SDK 0.10.5's panels show the
530
- stopped or starting package and check again on their own. A page no longer
531
- shows nothing for the first seconds after an upgrade (6.7 s in the regression test). Calls
532
- that change something, approvals and controls among them, still repeat for up
533
- to 30 s. `ctx.executor.status()` for a user who is not an administrator says
534
- the package is stopped or starting instead of `admin_required`.
535
- 繁中:status 類讀取遇停止/啟動中立即回傳該碼,交給 SDK 重試;核准等呼叫維持重試。
536
-
537
- ## 0.10.4 (2026-10-14)
538
-
539
- Builds with SDK 0.10.4 and templates 0.10.4: `init` writes SDK v0.10.4 (Go
540
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.4 and
541
- `@synopackageland/ui` 0.9.6. `check` and `build` still accept SDK 0.10.3 or
542
- later: nothing in this CLI needs 0.10.4, and an App on 0.10.3 never sees the
543
- stop and start codes the window now repeats. The executor digest table adds
544
- SDK 0.10.4, whose executor and lifecycle digests equal 0.10.3, so an App
545
- rebuilt on it keeps its approval unless its own root programs change. The
546
- gate refuses a stale SDK `ts/dist` and builds the executor flow's race binary
547
- before its timeout.
548
-
549
- 繁中摘要:使用 SDK 0.10.4 與 templates 0.10.4(UI 0.9.6);`check`/`build` 仍接受 SDK
550
- 0.10.3 以上。`ctx.call` 自動重試套件停止/啟動中的碼;非管理員的
551
- `ctx.executor.status()` 可讀到已核准的 executor。digest 表新增 SDK 0.10.4(與 0.10.3
552
- 相同),重建的 App 不需重新核准(除非自己的 root 程式改變)。
553
-
554
- - `ctx.call` repeats a call the package refused with `APP_STOPPED`,
555
- `APP_STARTING` or `EXECUTOR_STARTING` (the executor op was not sent): after
556
- 0.5 s, doubling to at most 8 s, for 30 s of waiting in all, so an App comes
557
- back by itself once the package has started. Other codes reject at once; past
558
- the budget the last refusal rejects; leaving the page cancels the wait.
559
- 繁中:套件停止/啟動中時 `ctx.call` 自動有界重試(最多等 30 s)。
560
- - The window approval of an upgrade (outdated tasks) makes 11 scheduler calls
561
- instead of 12: the outdated boot task is deleted by the approval's own first
562
- EventScheduler get, made moments before the password, instead of a second
563
- get (2.9 s on the test NAS in the regression test). The control task is still read again by id
564
- before its delete (spec/scheduler/v1). A fresh approval stays at 8.
565
- 繁中:升級核准的排程呼叫由 12 降為 11(boot 任務不再於刪除前重讀)。
566
- - A scope approval reads the executor's selection back every second at most
567
- (it backed off to 2 s), so the window sees the landed scope within a second
568
- of `scope.confirm` (the Dialog closed 4.8 s after it in the regression test). Removing
569
- the temporary selection task still lists Task Scheduler first, as
570
- spec/scheduler/v1 requires of a delete.
571
- 繁中:範圍核准讀回間隔上限 2 s → 1 s,confirmed 後 1 s 內看到結果。
572
- - `ctx.executor.status()` for a user who is not an administrator asks the
573
- App's `executor.status` (open to every signed-in user) instead of refusing
574
- before any request: the pinned, running executor reads `ready`; anything
575
- else stays `blocked` with `admin_required`. Task Scheduler is still read only
576
- for administrators. A user no longer sees an approved executor as waiting
577
- for approval.
578
- 繁中:非管理員的 `ctx.executor.status()` 會向 App 的 `executor.status` 詢問,已核准且
579
- 執行中即為 `ready`,不再一律回 `admin_required`。
580
-
581
- ## 0.10.3 (2026-10-14)
582
-
583
- Builds with SDK 0.10.3 and templates 0.10.3: `init` writes SDK v0.10.3 (Go
584
- module, Rust crate tag and `backend.sdk`), `@synopackageland/sdk` 0.10.3 and
585
- `@synopackageland/ui` 0.9.5. **Incompatible:** `check` and `build` need SDK
586
- 0.10.3 or later. The executor digest table adds SDK 0.10.3 and keeps 0.10.0
587
- to 0.10.2, so `inspect` still recognizes their SPKs. SDK 0.10.3's executor
588
- records the package version at every start, so an App rebuilt on it carries
589
- a new executor digest; when its policy digest does not change, the upgrade
590
- puts the new executor in place without approval. An App that declares
591
- `root.start` changes its policy and is approved again. The lifecycle digests
592
- equal 0.10.2.
593
-
594
- 繁中摘要:使用 SDK 0.10.3 與 templates 0.10.3(UI 0.9.5);`check`/`build` 需要 SDK
595
- 0.10.3 以上(不相容)。digest 表新增 SDK 0.10.3(保留 0.10.0~0.10.2)。policy digest
596
- 不變的 App 升級時自動換 executor;宣告 `root.start` 的 App 需要重新核准。lifecycle
597
- digest 與 0.10.2 相同。
598
-
599
- ### Added
600
-
601
- - `root.start` in dsm-package.yaml names a `root/` program as the executor's start step: the
602
- executor runs it as root once at the first start after each install or upgrade (`install`,
603
- or `upgrade` and the previous version), only while the approval is live, for at most 60
604
- seconds, and a failure never blocks the start. `build` writes it into the policy as `start`;
605
- `check` refuses a name outside `root/` and a program that also declares `run` or `job`
606
- (`STANDALONE_ROOT_PROGRAMS_INVALID`); the SDK executor's `check-policy` checks the rest.
607
- `inspect` lists the root steps. Declaring it changes the policy digest, so the App needs
608
- approval again; Apps without it keep their policy.
609
-
610
- 繁中摘要:`root.start` 指定一個 root 程式,executor 在每次安裝或升級後第一次啟動時以 root 執行一次;
611
- 失敗只記 log、不擋啟動。宣告後 policy digest 改變需重新核准,沒有宣告的 App 不受影響。
612
-
613
- - Lifecycle scripts: a project's `dsm-scripts/<DSM script name>` (`preinst`, `postinst`,
614
- `preupgrade`, `postupgrade`, `preuninst`, `postuninst`, `start-stop-status`) is packed as
615
- `scripts/app/<name>` and called from the generated script of the same name: after the SDK
616
- steps, and before them for `preupgrade`, `preuninst` and `stop`. It runs as the package
617
- account with DSM's arguments, environment and exit codes; a failed App `start` stops the SDK
618
- again. Native and Compose projects. `check` refuses other names (`APP_SCRIPT_UNKNOWN`),
619
- non-executable files and files without `#!`; `inspect` lists the App scripts. Projects
620
- without `dsm-scripts/` build unchanged. Guide: `synopkgland skill show lifecycle`.
621
-
622
- 繁中摘要:專案 `dsm-scripts/` 下以 DSM 原生檔名放的腳本會被打包,並在 CLI 產生的同名腳本中於
623
- SDK 步驟之後(preupgrade/preuninst/stop 在之前)以套件帳號執行;沒有該資料夾時行為不變。
624
-
625
- - Login Portal style: a portal App's launcher now carries DSM's `loginStyle` and `loginIcon`,
626
- so Control Panel > Login Portal > Applications shows a thumbnail instead of a blank box, and
627
- the alias login page gets a wallpaper, a button colour and the App icon. The CLI ships a
628
- neutral default (`assets/login-style/`); `login_style` in dsm-package.yaml overrides
629
- `color`, `wallpaper` with `wallpaper_thumbnail`, and `title`. `check` validates the images
630
- (`STANDALONE_LOGIN_STYLE_INVALID`, `STANDALONE_LOGIN_STYLE_IMAGE_INVALID`) and prints the
631
- style; `inspect` prints it and flags a portal SPK without one (`LOGIN_STYLE_MISSING`).
632
- Rebuilding an existing portal App with this CLI adds the default; no change to the project.
633
-
634
- 繁中摘要:有 portal 的 App 重新 build 後會自動帶入預設登入樣式,「登入入口 › 應用程式」縮圖不再
635
- 空白,別名登入頁有桌布、按鈕色與 App icon;可用 `login_style` 換主色、桌布與標題圖。
636
-
637
- ### Changed
638
-
639
- - **Incompatible:** the CLI needs SDK 0.10.3 or later. `check` and `build` refuse an App
640
- whose backend requires SDK 0.10.0, 0.10.1 or 0.10.2 (`SDK_UNSUPPORTED`, naming 0.10.3);
641
- raise the Go module, Cargo tag or `backend.sdk` and the frontend's
642
- `@synopackageland/sdk` to 0.10.3. The CLI no longer carries the fallbacks for those
643
- releases: the pre-`timeouts.json` timeouts, skipping `executor check-policy` for an SDK
644
- without it, and `EXECUTOR_START_STEP_UNSUPPORTED`.
645
-
646
- 繁中摘要:CLI 需要 SDK 0.10.3 以上;App 若仍用 SDK 0.10.0~0.10.2,`check`/`build` 會以
647
- `SDK_UNSUPPORTED` 拒絕。移除為舊版保留的逾時退回值、check-policy 略過與 start 步驟檢查。
648
-
649
- - `check` and `build` refuse a request deadline that ends before a root program `root.run`
650
- may run would answer (`STANDALONE_REQUEST_TIMEOUT_SHORT`): `backend.timeout_seconds`
651
- (60 by default) must be at least the program's `run.timeout_s` (20 by default) + 20, the
652
- executor's 10 seconds and its client's 10 (SDK `spec/protocol/v1`, Timeouts). The default
653
- covers programs of up to 40 seconds; a 120-second program needs `timeout_seconds: 140`.
654
- A program with only `job`, `start` or `stop` is not affected.
655
- - The request default, the window's wait past the request deadline and the `root.run`
656
- deadline check read their values from the App's SDK, `spec/protocol/v1/timeouts.json`,
657
- instead of the CLI's own copies, so they follow the SDK the App builds with. An App
658
- without the SDK keeps 60 seconds, 2 seconds, and a `run.timeout_s` of 20 + 20. The
659
- generated values do not change with SDK 0.10.3.
660
-
661
- 繁中摘要:request 預設逾時、視窗多等的時間與 root.run 逾時檢查改讀 App 所用 SDK 的
662
- timeouts.json,不再用 CLI 手抄的值;沒有 SDK 的 App 沿用 60 秒、2 秒、20 + 20。
663
- - The DSM window's approval and `control start` wait for an executor that answers
664
- `state: "starting"` while it runs its start step, for up to 75 seconds, instead of giving
665
- up after 30 seconds; past that the result is blocked with code `executor_starting`, and the
666
- plain-UI panel and the SDK settings say the service is still starting. The plain-UI panel
667
- says it is waiting for the background service while a request runs.
668
-
669
- 繁中摘要:核准與 control start 會等待回報「starting」的 executor(最多 75 秒),超過則以
670
- `executor_starting` 阻擋並說明仍在啟動。
671
-
672
- - The DSM window's approval asks DSM's schedulers 8 times instead of 15, following the SDK's
673
- `spec/scheduler/v1`. The boot task is read with one EventScheduler `get` by its name
674
- (DSM keeps event task names unique) and never listed; Task Scheduler is listed once before
675
- and once after the writes, and the control task is then read by its listed id. A step reads
676
- each task once and reuses that read; a delete still reads the task again first. After the
677
- boot task runs, only `executor.status` is asked: the tasks were read back exactly just
678
- before, and the executor's pins answer for the rest. `executor.status` is asked every
679
- 500 ms for the first 4 seconds, then backs off to 2 seconds. `status` (and `check`) gets the
680
- boot task by name instead of listing EventScheduler. Measured on a DSM 7.4 x86_64 NAS with
681
- a fresh install: ready came 1.1-4.9 s after the boot task ran instead of 6.3-10.9 s, and
682
- 10.1-21.8 s after Approve instead of 16.3-26.9 s (DSM's scheduler calls vary by seconds).
683
-
684
- 繁中摘要:視窗核准對 DSM 排程 API 的呼叫從 15 次降為 8 次:boot 任務改用名稱 get、不再 list
685
- EventScheduler;Task Scheduler 在寫入前後各 list 一次,之後用 id get;執行 boot 任務後只問
686
- `executor.status`,前 4 秒每 500 ms 問一次。安全檢查(重複、內容逐字比對、pins)都保留。
687
-
688
- ### Fixed
689
-
690
- - The DSM window's `openFileManager` and `revealFile` take a `/volumeN/...` path as the SDK's
691
- `fileStationPath` does: a DSM volume mount (`/volumeN`, `/volumeUSBN`, `/volumeSATAN`) is
692
- stripped instead of refused, and the reveal recovery follows the path File Station got.
693
- Anything but one canonical shared-folder path is still `invalid-input`.
694
- - Executor approval results carry a structured `code` beside the English `reason`, and an
695
- `outcome` when the approval rolled back: a refused administrator password is
696
- `password_rejected` (DSM 3121), other PasswordConfirm refusals `password_required`,
697
- `session_ended` or `password_confirm_failed`; scheduler, task and executor failures have
698
- codes of their own (`scheduler_denied`, `task_missing`, `executor_unreachable`, …) and the
699
- outcomes are `restored`, `withdrawn`, `restore_failed` and `withdraw_failed`. The SDK's
700
- `ExecutorSettings` reads `code`. The plain-UI window panel now shows the code and outcome in
701
- the DSM language, followed by the DSM code (`dsm_3121`), instead of the English reason.
702
- Rebuild the App to pick this up.
703
-
704
- 繁中摘要:核准結果新增結構化 `code`(例如 `password_rejected`)與回復結果 `outcome`,`reason`
705
- 仍保留英文細節;純 UI 視窗面板改以 DSM 語系顯示原因,不再直接顯示英文;需重新 build App。
706
-
707
- - Executor approval: a scheduler write that fails partway no longer leaves the earlier
708
- approval half deleted. The approval stops at the first failed write, withdraws the tasks
709
- it created and recreates the earlier boot and control tasks it removed, then offers
710
- approval again. DSM cannot hold two same-named tasks, so the old task is still deleted
711
- before its replacement is created. When the rollback itself fails, the window says so, and
712
- the next approval converges from what is left. A DSM session that ended (`dsm_106`,
713
- `dsm_107`, `dsm_119`) asks the administrator to sign in to DSM again.
714
-
715
- 繁中摘要:核准半途寫入失敗時,會撤回這次建立的排程並重建先前核准的舊排程,再提供重新核准;
716
- 還原失敗時會明白說明,下次核准可自動收斂。DSM session 失效時提示重新登入。
717
-
718
- - An App opened through its Application Portal alias (`https://nas/<alias>/`) reaches its
719
- backend again. The generated window posted `ctx.call` and `ctx.files` to the absolute
720
- `/webman/3rdparty/<package>/cgi/api.cgi`, which DSM answers with 403 on the alias, so every
721
- call failed with `bad_response` and an approved executor read as unavailable. The path is
722
- now relative to the page, as the App's own assets are; the DSM desktop resolves it to the
723
- same URL as before. Rebuild the App to pick this up.
724
-
725
- 繁中摘要:從應用程式入口別名網址開啟 App 時,`ctx.call`/`ctx.files` 改用相對路徑,不再被
726
- DSM 以 403 拒絕(原本顯示 `bad_response`、已核准的 executor 顯示無法使用);需重新 build App。
727
-
728
- - `check` and `build` refusing a `root.start`, `root.stop` or `uninstall.purge` program that
729
- also declares `run` or `job` (`EXECUTOR_POLICY_REJECTED`) now say what to do: give the step
730
- another program name in `root.programs`, one without `run` or `job`.
731
-
732
- 繁中摘要:start/stop/purge 的 program 同時宣告 run/job 而被拒時,提示改用另一個沒有
733
- run/job 的 program 名稱。
734
-
735
- - `init` and `build` of a Python App whose SDK has no Python SDK (`python/synopkgland_app`)
736
- report `STANDALONE_PYTHON_SDK_MISSING` with the path, instead of a bare `ENOENT`.
737
-
738
- 繁中摘要:找不到 Python SDK 時改回專屬錯誤碼 `STANDALONE_PYTHON_SDK_MISSING`,不再是 `ENOENT`。
739
-
740
- ### Removed
741
-
742
- - `@synopackageland/cli/authorization-sdk` is gone: `ROOT_RUNTIME` and
743
- `AUTHORIZATION_RUNTIME`, the window runtimes of a native product with its own pinned root
744
- tasks, ship unchanged in `@synopackageland/sdk/native`. A native product imports them from
745
- the SDK; generated Apps are not affected.
746
-
747
- 繁中摘要:native 產品的 root/authorization 視窗 runtime 改由 SDK(`@synopackageland/sdk/native`)提供,
748
- CLI 不再匯出 `authorization-sdk`;一般產生的 App 不受影響。
749
-
750
- - The CLI's copy of the Application Portal panel text (19 keys in 9 languages) is gone:
751
- `build` reads it from the App's SDK (`ts/i18n/portal/<locale>.json`, in every SDK 0.10.x),
752
- the same text the SDK's `PortalSettings` shows. The built window is unchanged; a fix to the
753
- text reaches an App with its next SDK.
754
-
755
- 繁中摘要:Application Portal 面板文案改從 App 的 SDK 讀取,CLI 不再保留一份;建出的視窗不變,文案修正隨 SDK 更新。
756
-
757
- - `dist/runtime/delegate-contract.js` keeps only the operation names the build checks; the
758
- background-save DTOs, route table and limits, which nothing in the CLI used, and the frozen
759
- v1 vector now live only in the SDK (`ts/src/delegate/long-running.ts`).
760
-
761
-
762
- ## 0.10.2 (2026-10-14)
763
-
764
- First release of the changelog. `@synopackageland/cli` (`synopkgland`)
765
- builds Synology DSM 7 packages (SPK) from a template, checks them, and
766
- installs them on a NAS. It builds with SDK 0.10.2 and templates 0.10.2.
767
-
768
- 繁中摘要:`synopkgland` 從模板建立 DSM 7 套件(SPK),檢查、打包並安裝到
769
- NAS;搭配 SDK 0.10.2 與 templates 0.10.2。
770
-
771
- ### Templates
772
-
773
- - `native-go`: a Go backend with a Vue UI (or `--ui plain`).
774
- - `native-python`, `native-node`, `native-rust`: the same App with a Python,
775
- Node.js (TypeScript) or Rust backend behind the SDK's Go runtime.
776
- - `binary`: a static Linux program you build yourself.
777
- - `compose`: an existing Docker Compose app (`--from compose.yaml`).
778
- - Existing native products keep their own payload layout with
779
- `backend: {kind: native}`.
780
-
781
- ### Capabilities
782
-
783
- `init --with …` or `add` adds `files`, `jobs`, `login`, `delegate`,
784
- `root-ops` and `portal`. `delegate` and `root-ops` run through the SDK's
785
- executor, which an administrator approves once by digest; `check` and
786
- `build` have the App's executor validate its policy first
787
- (`EXECUTOR_POLICY_REJECTED`).
788
-
789
- ### Commands
790
-
791
- - `init`, `add`: create a project and add capabilities.
792
- - `check`, `build`: validate the manifest and build the SPK for one or more
793
- architectures, with the executor checked against the digests shipped in the
794
- CLI.
795
- - `inspect`: check any SPK against the DSM 7 package format without
796
- installing it.
797
- - `deploy`, `status`, `logs`: install on a NAS and read its state.
798
- - `dev`, `test`, `images`: run, test and pin images for Compose projects.
799
- - `skill`: the bundled guidance for AI assistants.
800
-
801
- `--json` prints one JSON object on stdout; failures carry a stable `code`
802
- and a `hint`.
5
+ - Attribute upstream Apps to their original project/team or owner and the packaging publisher as distributor, with generic skill guidance and a release checklist
6
+ - Prefill upstream-fork attribution from the upstream repository and publisher options or environment configuration; use a bounded public GitHub owner lookup and explicit TODOs offline
7
+ - Warn when upstream attribution is incomplete or the maintainer equals the distributor; warnings appear in check JSON and keep exit status successful
8
+ - Keep templates 0.31.x and its SDK defaults; CLI minor releases can ship independently of templates
package/README.md CHANGED
@@ -29,6 +29,14 @@ synopkgland status --host my-nas --json
29
29
  `deploy --dry-run` prints the deployment plan without connecting to a NAS. You can also install
30
30
  the SPK with Package Center's Manual Install.
31
31
 
32
+ When packaging someone else's open-source App, name its original project/team
33
+ (or owner display name, then login) as maintainer and yourself as distributor.
34
+ Use `init --upstream-fork --upstream <repo-url> --publisher <name> --publisher-url <url>`;
35
+ `--upstream-maintainer` records a verified project/team name. Publisher defaults
36
+ come from `SYNOPKGLAND_PUBLISHER` / `SYNOPKGLAND_PUBLISHER_URL`. Offline lookups
37
+ leave TODOs; `check --json` reports attribution warnings without failing.
38
+ See the [upstream scaffold and release checklist](skills/reference/upstream-fork.md).
39
+
32
40
  ## Templates
33
41
 
34
42
  | Template | Use it for | Manifest |
@@ -62,12 +70,12 @@ alongside npm lockfiles. Do not disable checksum verification for public SDK
62
70
  packages. Private mirrors can use explicitly configured Go authentication and
63
71
  checksum settings; a failed fetch reports `SDK_DOWNLOAD_FAILED`.
64
72
 
65
- CLI-only patch releases can differ from the SDK patch. Compose import and SDK defaults
73
+ CLI-only releases can differ from the SDK patch. Compose import and SDK defaults
66
74
  read the SDK pin from the installed templates; explicit App SDK declarations take precedence.
67
75
 
68
76
  `init` and `add` read their templates from the locally installed
69
- `@synopackageland/templates`. CLI 0.31.0 depends on `~0.31.0`: npm installation or
70
- update can select the latest templates patch in the 0.29 minor line. Templates
77
+ `@synopackageland/templates`. CLI 0.32.0 depends on `~0.31.0`: npm installation or
78
+ update can select the latest templates patch in the 0.31 minor line. Templates
71
79
  patches need only a templates release; update the npm installation to receive them.
72
80
  The CLI never fetches templates at runtime, so offline use works after installation;
73
81
  commit a lockfile and use `npm ci` to reproduce a chosen dependency tree.
package/dist/cli.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { upstreamRepository, upstreamMaintainer } from './upstream-attribution.js';
1
2
  import { Command, CommanderError } from "commander";
2
3
  import { existsSync, readFileSync, renameSync, statSync } from "node:fs";
3
4
  import { createHash } from "node:crypto";
@@ -324,6 +325,8 @@ export async function runCli(argv) {
324
325
  throw failure(validation.errors[0].code, formatErrors(validation.errors), validation.errors[0].suggestion);
325
326
  const standalone = standaloneRoot(rootOf(command));
326
327
  if (standalone) {
328
+ if (validation.warnings?.length)
329
+ console.error(formatErrors(validation.warnings));
327
330
  const loaded = isNativeLayout(standalone)
328
331
  ? loadNativeLayout(standalone)
329
332
  : loadStandaloneManifest(standalone);
@@ -348,6 +351,7 @@ export async function runCli(argv) {
348
351
  : undefined;
349
352
  emit("check", {
350
353
  package: loaded.manifest.package,
354
+ warnings: validation.warnings ?? [],
351
355
  ...("installSettings" in loaded.manifest && loaded.manifest.installSettings ? { installSettings: loaded.manifest.installSettings } : {}),
352
356
  ...("identityGateway" in loaded.manifest && gatewayAdmissionReport(loaded.manifest.identityGateway, loaded.manifest.entry) ? { identityGateway: gatewayAdmissionReport(loaded.manifest.identityGateway, loaded.manifest.entry) } : {}),
353
357
  capabilities: "capabilities" in loaded.manifest
@@ -442,6 +446,10 @@ export async function runCli(argv) {
442
446
  .option("--package <id>", "INFO.package (defaults to the directory name)")
443
447
  .option("--name <display>", "Display name")
444
448
  .option("--upstream-image <repository>", "compose fork: official image repository; upgrade pins its matching tag and index digest")
449
+ .option("--upstream <url>", "Original upstream repository URL (defaults to the upstream Git remote)")
450
+ .option("--upstream-maintainer <name>", "Verified original project/team name; otherwise resolve the GitHub owner")
451
+ .option("--publisher <name>", "Your distributor name (or SYNOPKGLAND_PUBLISHER)")
452
+ .option("--publisher-url <url>", "Your distributor URL (or SYNOPKGLAND_PUBLISHER_URL)")
445
453
  .option("--upstream-fork", "Create only synology/ inside an upstream source fork (compose or native-go)")
446
454
  .option("--health-path <path>", "compose: actual readiness endpoint; redirects are not followed")
447
455
  .option("--health-status <status>", "compose: expected 2xx readiness status (no default)", Number)
@@ -455,7 +463,7 @@ export async function runCli(argv) {
455
463
  .option("--min-dsm <version>", `Minimum DSM version (native templates and binary default to ${DEFAULT_MIN_DSM}, compose to ${CONTAINER_MIN_DSM})`)
456
464
  .argument("[dir]", "Target directory", ".")
457
465
  .addHelpText("after", "\nNative projects: native-go, native-python, native-node, native-rust and binary use dsm-package.yaml schema 2.\nCompose projects: compose uses dsm-package.yaml schema 2; --from imports compose.yaml.\nQuick start: synopkgland init -t native-go --with files,jobs,login my-app\n cd my-app && synopkgland build\n")
458
- .action((dir, options, command) => {
466
+ .action(async (dir, options, command) => {
459
467
  if (command.parent?.opts().project)
460
468
  throw failure("CLI_OPTION_INVALID", "--project does not apply to init.");
461
469
  if (!options.template)
@@ -463,7 +471,16 @@ export async function runCli(argv) {
463
471
  if (!isTemplateName(options.template))
464
472
  throw failure("INIT_TEMPLATE_UNKNOWN", `Unknown template: ${options.template}`);
465
473
  const targetDir = resolve(dir);
474
+ const upstream = options.upstreamFork ? upstreamRepository(targetDir, options.upstream) : undefined;
475
+ if (options.upstream && !upstream)
476
+ throw failure("INIT_UPSTREAM_INVALID", "Supply an http(s) repository URL without credentials");
477
+ if (!options.upstreamFork && (options.upstream || options.upstreamMaintainer || options.publisher || options.publisherUrl))
478
+ throw failure("INIT_OPTIONS_UNSUPPORTED", "Attribution options require --upstream-fork");
466
479
  initFromTemplate(options.template, targetDir, {
480
+ upstream,
481
+ upstreamMaintainer: options.upstreamMaintainer ?? (options.upstreamFork ? await upstreamMaintainer(upstream) : undefined),
482
+ publisher: options.publisher ?? process.env.SYNOPKGLAND_PUBLISHER,
483
+ publisherUrl: options.publisherUrl ?? process.env.SYNOPKGLAND_PUBLISHER_URL,
467
484
  upstreamFork: options.upstreamFork,
468
485
  upstreamImage: options.upstreamImage,
469
486
  healthPath: options.healthPath,
@@ -1,6 +1,9 @@
1
1
  import { COMPOSE_ERRORS } from "./standalone/compose-errors.js";
2
2
  export const ERROR_REGISTRY = {
3
3
  ...COMPOSE_ERRORS,
4
+ INIT_UPSTREAM_INVALID: { code: "INIT_UPSTREAM_INVALID", description: "The upstream repository URL is invalid.", suggestion: "Supply --upstream with an http(s) repository URL without credentials." },
5
+ INIT_PUBLISHER_INVALID: { code: "INIT_PUBLISHER_INVALID", description: "The attribution name or publisher URL is invalid.", suggestion: "Use plain single-line project/publisher names and an http(s) publisher URL without credentials." },
6
+ UPSTREAM_ATTRIBUTION: { code: "UPSTREAM_ATTRIBUTION", description: "Upstream App attribution is incomplete or names the distributor as maintainer.", suggestion: "Name the original project/team or owner as maintainer; set metadata.maintainer_url to the upstream repository and metadata.distributor/distributor_url to your publisher; resolve TODOs before release." },
4
7
  INSTALL_SETTINGS_INVALID: { "code": "INSTALL_SETTINGS_INVALID", "description": "The installation setting declaration is invalid.", "suggestion": "Declare 1–32 lowercase identifier keys in install.settings, each with type https-origin, a plain label and an explicit boolean required; omit defaults and values." },
5
8
  SDK_INSTALL_SETTINGS_UNSUPPORTED: { "code": "SDK_INSTALL_SETTINGS_UNSUPPORTED", "description": "The selected SDK does not provide native installation settings.", "suggestion": "Pin SDK 0.24.0 or a matching supported release with installsettings/cmd/control, then rebuild with the matching CLI." },
6
9
  SDK_INSTALL_SETTINGS_BUILD_FAILED: { "code": "SDK_INSTALL_SETTINGS_BUILD_FAILED", "description": "The SDK installation settings controller could not be built.", "suggestion": "Install the pinned Go toolchain, inspect the compiler diagnostics and rebuild with a complete matching SDK release." },
@@ -1,6 +1,10 @@
1
1
  export interface ForkRecipeOptions {
2
2
  upstreamImage?: string;
3
3
  from?: string;
4
+ upstream?: string;
5
+ upstreamMaintainer?: string;
6
+ publisher?: string;
7
+ publisherUrl?: string;
4
8
  }
5
9
  export declare function validateForkRecipe(template: 'compose' | 'native-go', options: ForkRecipeOptions): void;
6
10
  /** CLI-owned packaging recipe; upstream files and history stay untouched. */
@@ -3,8 +3,17 @@ import { join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { parseDocument } from 'yaml';
5
5
  import { API_PERMISSIONS_FILE } from './standalone/api-permissions.js';
6
+ import { safeMetadataUrl } from './package-metadata.js';
6
7
  import { parseImageReference } from './images/pin.js';
7
8
  export function validateForkRecipe(template, options) {
9
+ if (options.upstream && !safeMetadataUrl(options.upstream))
10
+ throw new Error('[INIT_UPSTREAM_INVALID] Supply an http(s) upstream repository URL without credentials.');
11
+ if (options.publisherUrl && !safeMetadataUrl(options.publisherUrl))
12
+ throw new Error('[INIT_PUBLISHER_INVALID] Supply an http(s) publisher URL without credentials.');
13
+ for (const value of [options.publisher, options.upstreamMaintainer]) {
14
+ if (value !== undefined && (!value.trim() || /[\x00-\x1f\x7f"\\]/.test(value) || value.length > 200))
15
+ throw new Error('[INIT_PUBLISHER_INVALID] Attribution names must be plain, non-empty single-line text.');
16
+ }
8
17
  if (template === 'native-go' && options.upstreamImage)
9
18
  throw new Error('[INIT_OPTIONS_UNSUPPORTED] Native forks stage official release binaries or use a source build hook; image options apply only to compose.');
10
19
  if (template !== 'compose')
@@ -28,6 +37,14 @@ export function writeForkScaffold(packaging, template, options) {
28
37
  ? { kind: 'official-image', repository: options.upstreamImage, service, imageTag: 'tag' }
29
38
  : { kind: 'native-source' };
30
39
  writeFileSync(join(packaging, 'upstream-recipe.json'), JSON.stringify(recipe, null, 2) + '\n');
40
+ doc.set('maintainer', options.upstreamMaintainer ?? 'TODO: original upstream project or owner');
41
+ doc.setIn(['metadata', 'distributor'], options.publisher ?? 'TODO: your publisher name');
42
+ if (options.upstream) {
43
+ doc.setIn(['metadata', 'upstream'], options.upstream);
44
+ doc.setIn(['metadata', 'maintainer_url'], options.upstream);
45
+ }
46
+ if (options.publisherUrl)
47
+ doc.setIn(['metadata', 'distributor_url'], options.publisherUrl);
31
48
  doc.set('entry', 'admin-only');
32
49
  doc.delete('ctl');
33
50
  if (official) {
@@ -53,6 +70,8 @@ export function writeForkScaffold(packaging, template, options) {
53
70
  const guidance = `
54
71
  ## Integrating an existing open-source App
55
72
 
73
+ If you package someone else's open-source project, set maintainer to the original project/owner (prefer the project's own team/org name, else the owner's display name, else login), metadata.maintainer_url to the upstream repo, and yourself as metadata.distributor/metadata.distributor_url. Resolve all attribution TODOs before release; run check and review its warnings. Only your own Apps use your publisher as maintainer.
74
+
56
75
  Keep the upstream fork/history and remote upstream. All wrapper-owned files belong in synology/; never edit upstream files or treat the App as an SDK component. Read synopkgland skill show upstream-fork.
57
76
 
58
77
  Leave ctl absent from the manifest by default. Ask whether the user wants AI control; add ctl only when explicitly requested. If requested, read synopkgland skill show ctl and produce ctl-plan covering existing UI actions and public API endpoints, including common reads and writes. Status-only commands do not meet completion. Wrap existing native APIs without changing upstream code or inventing features. Long operations must return through existing asynchronous APIs without waiting for completion; report synchronous-only long APIs as blockers for discussion.
package/dist/init.d.ts CHANGED
@@ -18,6 +18,10 @@ export interface InitOptions {
18
18
  from?: string;
19
19
  upstreamFork?: boolean;
20
20
  upstreamImage?: string;
21
+ upstream?: string;
22
+ upstreamMaintainer?: string;
23
+ publisher?: string;
24
+ publisherUrl?: string;
21
25
  healthPath?: string;
22
26
  healthStatus?: number;
23
27
  /** native-go: `install.sharedFolder` (select from the NAS, or create one). key defaults to "shared", permission to "rw". */
@@ -7,6 +7,7 @@ export interface PackageMetadata {
7
7
  support_email?: string;
8
8
  homepage?: string;
9
9
  source_url?: string;
10
+ upstream?: string;
10
11
  issues_url?: string;
11
12
  license?: string;
12
13
  maintainer_url?: string;
@@ -27,6 +27,7 @@ export function validateMetadata(raw) {
27
27
  "support_email",
28
28
  "homepage",
29
29
  "source_url",
30
+ "upstream",
30
31
  "issues_url",
31
32
  "license",
32
33
  "maintainer_url",
@@ -66,6 +67,7 @@ export function validateMetadata(raw) {
66
67
  if ([
67
68
  "homepage",
68
69
  "source_url",
70
+ "upstream",
69
71
  "issues_url",
70
72
  "maintainer_url",
71
73
  "distributor_url",
@@ -0,0 +1,7 @@
1
+ import type { ValidationError } from './types.js';
2
+ /** A repository URL is provenance, never a credential-bearing transport URL. */
3
+ export declare function upstreamRepository(root: string, explicit?: string): string | undefined;
4
+ /** Best effort, bounded and public: offline/rate-limited lookups retain an explicit TODO. */
5
+ export declare function upstreamMaintainer(url?: string, fetcher?: typeof fetch): Promise<string | undefined>;
6
+ /** Advice only: attribution never changes manifest validity or the command exit status. */
7
+ export declare function upstreamAttributionWarnings(root: string): ValidationError[];
@@ -0,0 +1,56 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { parse } from 'yaml';
5
+ import { safeMetadataUrl } from './package-metadata.js';
6
+ /** A repository URL is provenance, never a credential-bearing transport URL. */
7
+ export function upstreamRepository(root, explicit) {
8
+ const raw = explicit ?? spawnSync('git', ['remote', 'get-url', 'upstream'], {
9
+ cwd: root, encoding: 'utf8', timeout: 1000,
10
+ }).stdout?.trim();
11
+ if (!raw)
12
+ return undefined;
13
+ const url = raw.replace(/^git@github\.com:/, 'https://github.com/').replace(/\.git$/, '');
14
+ return safeMetadataUrl(url) ? url : undefined;
15
+ }
16
+ /** Best effort, bounded and public: offline/rate-limited lookups retain an explicit TODO. */
17
+ export async function upstreamMaintainer(url, fetcher = fetch) {
18
+ if (!url)
19
+ return undefined;
20
+ const match = /^https:\/\/github\.com\/([\w-]+)\/[^/?#]+\/?$/.exec(url);
21
+ if (!match)
22
+ return undefined;
23
+ try {
24
+ const response = await fetcher(`https://api.github.com/users/${match[1]}`, {
25
+ headers: { Accept: 'application/vnd.github+json' }, signal: AbortSignal.timeout(3000),
26
+ });
27
+ if (!response.ok)
28
+ return undefined;
29
+ const owner = await response.json();
30
+ const name = typeof owner.name === 'string' && owner.name.trim() ? owner.name.trim() : owner.login;
31
+ return typeof name === 'string' && name.length <= 200 && !/[\x00-\x1f\x7f"\\]/.test(name) ? name : undefined;
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ /** Advice only: attribution never changes manifest validity or the command exit status. */
38
+ export function upstreamAttributionWarnings(root) {
39
+ const raw = parse(readFileSync(join(root, 'dsm-package.yaml'), 'utf8'));
40
+ const m = raw?.metadata ?? {};
41
+ const fork = existsSync(join(root, 'upstream-recipe.json')) ||
42
+ (basename(root) === 'synology' && existsSync(join(root, 'upgrade.sh')));
43
+ if (!m.upstream && !fork)
44
+ return [];
45
+ const todo = (v) => typeof v !== 'string' || !v.trim() || /^TODO\b/i.test(v.trim());
46
+ if (!todo(raw.maintainer) && !todo(m.distributor) &&
47
+ raw.maintainer.trim().toLowerCase() !== m.distributor.trim().toLowerCase() &&
48
+ !todo(m.maintainer_url) && !todo(m.distributor_url))
49
+ return [];
50
+ return [{
51
+ code: 'UPSTREAM_ATTRIBUTION', severity: 'warning',
52
+ span: { file: 'dsm-package.yaml', line: 1, column: 1 },
53
+ message: 'Upstream App attribution is incomplete or names the distributor as maintainer',
54
+ suggestion: "Set maintainer to the original project/team, else the owner's display name or login; set metadata.maintainer_url to the upstream repository and metadata.distributor/distributor_url to your publisher. Resolve attribution TODOs before release",
55
+ }];
56
+ }
package/dist/validate.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { upstreamAttributionWarnings } from './upstream-attribution.js';
1
2
  import { existsSync } from "node:fs";
2
3
  import { join } from "node:path";
3
4
  import { getErrorCode } from "./error-registry.js";
@@ -36,6 +37,6 @@ export function validateProject(project, _options = {}) {
36
37
  "Correct the schema-2 declaration; see the SDK Compose support matrix.",
37
38
  })),
38
39
  }
39
- : { ok: true, errors: [] };
40
+ : { ok: true, errors: [], warnings: upstreamAttributionWarnings(project.root) };
40
41
  }
41
42
  export { findProjectRoot } from "./discovery.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -96,6 +96,11 @@
96
96
  "pattern": "^https://",
97
97
  "maxLength": 2048
98
98
  }
99
+ },
100
+ "upstream": {
101
+ "type": "string",
102
+ "maxLength": 2048,
103
+ "pattern": "^https?://"
99
104
  }
100
105
  }
101
106
  }
@@ -188,6 +188,11 @@
188
188
  "type": "object",
189
189
  "additionalProperties": false,
190
190
  "properties": {
191
+ "upstream": {
192
+ "type": "string",
193
+ "maxLength": 2048,
194
+ "pattern": "^https?://"
195
+ },
191
196
  "changelog": {
192
197
  "oneOf": [
193
198
  {
package/skills/SKILL.md CHANGED
@@ -5,7 +5,7 @@ description: Initialize, extend, check, build, inspect, and deploy Synology appl
5
5
 
6
6
  # Independent Synology SPKs
7
7
 
8
- CLI 0.27.x installs templates through `~0.27.0`. A templates patch needs only a
8
+ CLI 0.32.0 installs templates through `~0.31.0`. A templates patch needs only a
9
9
  templates release; npm install/update selects a patch in that minor line. The CLI
10
10
  uses the locally installed templates and reads their SDK pin for defaults; it
11
11
  never downloads templates at runtime. Preserve the lockfile and use `npm ci` for
@@ -22,6 +22,14 @@ Use this guide to initialize, build and inspect own applications or upstream OSS
22
22
 
23
23
  For an existing open-source App, first read [reference/upstream-fork.md](reference/upstream-fork.md): upstream fork/history + untouched upstream files + all packaging in `synology/`, official container images pinned by tag/index digest, native official release binaries pinned by SHA-256 or a source build hook, and a single tag-merge upgrade command. Use `init --upstream-fork`; never rewrite the App as a component. Integrated upstream Apps require direct DSM administrator entry without a separate App password or an OIDC/SSO server: supported trusted headers first, disabling native login second; the shipped OpenList pattern uses the existing SDK 0.17.3 token adapter (`mode: token`, `access: dsm-admins`, `entry: admin-only`) with server-side Authorization injection. All DSM admins share one upstream admin; token reset requires package restart; WebDAV retains its native credentials.
24
24
 
25
+ **If you package someone else's open-source project, set `maintainer` to the
26
+ original project/owner (prefer the project's own team/org name, else the owner's
27
+ display name, else login), `metadata.maintainer_url` to the upstream repository,
28
+ and yourself as `metadata.distributor` / `metadata.distributor_url`.** Only your
29
+ own Apps use your publisher as maintainer. Pass upstream and publisher attribution
30
+ to `init --upstream-fork`; resolve TODOs and review `check` warnings before release.
31
+ See the scaffold steps and release checklist in [reference/upstream-fork.md](reference/upstream-fork.md).
32
+
25
33
  For upstream OSS packaging, leave `ctl` absent from the manifest by default. Ask whether the user wants AI control; implement it only when explicitly requested. Read [reference/ctl.md](reference/ctl.md) for the required `ctl-plan`, UI/API coverage checklist and asynchronous-operation rules. Own Apps retain the native template's ctl defaults, with commands covering their main features; status-only coverage is incomplete.
26
34
 
27
35
  Start with `synopkgland init -t native-go`, `synopkgland init -t binary`, or `synopkgland init -t compose`. Read reference/create.md or reference/wrap.md, then reference/deploy.md. Native CGI session checks belong to the package. For wrapping upstream OSS with DSM accounts, read [reference/identity-gateway.md](reference/identity-gateway.md) first. For all Compose work, read [reference/compose.md](reference/compose.md): schema-2 upstream files, generated override, SDK planner, fixed package identity and current DSM blockers. Containers do not inherit a signed-in user's File Station ACL. Existing Shared Folder mounts are blocked until official protected ACL/project control is verified.
@@ -247,7 +247,7 @@ declared availability readiness, LAN isolation/egress, protected grant lifecycle
247
247
  ACLs, two-user visibility and clean install/upgrade/reboot/stop/uninstall before
248
248
  calling the package supported on a NAS. No NAS is accessed by the build gate.
249
249
 
250
- The unified Compose contract uses SDK 0.27.x with CLI 0.27.x and locally installed templates from `~0.27.0`. Templates patches can ship independently; npm install/update resolves them, and SDK defaults come from the installed templates. SDK minor upgrades do not require a CLI release when they declare supported executor manifest format 1; the resolved SDK owns the release mapping and verified payload digests. The CLI does not fetch templates at runtime. The legacy packaging route and bridge publish mode are removed.
250
+ The unified Compose contract uses SDK defaults from locally installed templates `~0.31.0` with CLI 0.32.0. Templates patches can ship independently; npm install/update resolves them, and SDK defaults come from the installed templates. SDK minor upgrades do not require a CLI release when they declare supported executor manifest format 1; the resolved SDK owns the release mapping and verified payload digests. The CLI does not fetch templates at runtime. The legacy packaging route and bridge publish mode are removed.
251
251
 
252
252
  Primary override files, and each included override set, must share a directory. Cross-directory override bases are rejected before loader I/O. Included implicit .env files are confined, inventoried and preserved; symlinks are rejected.
253
253
 
@@ -11,6 +11,30 @@ under `synology/`, such as DSM install wizards and gateway pages; leave upstream
11
11
  UI/locale strings and release notes unchanged, and exclude upstream source from
12
12
  any future punctuation checker.
13
13
 
14
+ ## Maintainer and distributor
15
+
16
+ If you package someone else's open-source project, set `maintainer` to the original
17
+ project/owner (prefer the project's own team/org name, else the owner's display
18
+ name, else login), `metadata.maintainer_url` to the upstream repository, and
19
+ **yourself** as `metadata.distributor` / `metadata.distributor_url`. Only your own
20
+ Apps use your publisher as maintainer. Syno Package Land is the distributor only
21
+ when it distributes the App; this rule applies to every third-party developer.
22
+
23
+ Pass `--upstream <repo-url>` (or retain the `upstream` Git remote), your verified
24
+ `--upstream-maintainer <project-team>` when known, and `--publisher <name>` /
25
+ `--publisher-url <url>` to `init --upstream-fork`. Publisher defaults come from
26
+ `SYNOPKGLAND_PUBLISHER` / `SYNOPKGLAND_PUBLISHER_URL`. The CLI tries the public
27
+ GitHub owner profile with a bounded lookup, using its display name then login.
28
+ Offline or unsupported hosts leave an explicit maintainer TODO. Missing publisher
29
+ configuration leaves a distributor TODO. Resolve these manually before release.
30
+ `metadata.upstream` records the repository; `check` warns, without failing, for
31
+ missing attribution or a maintainer that equals the distributor. The fork layout
32
+ also enables this warning without `metadata.upstream`.
33
+
34
+ These metadata fields become standard SPK INFO fields; upstream source stays
35
+ unchanged. Catalog attribution may change before rebuilding; installed INFO
36
+ changes only after an SPK upgrade.
37
+
14
38
  ## Decide whether to expose AI control
15
39
 
16
40
  Ask whether the user needs AI control unless they have already explicitly requested it.
@@ -37,11 +61,12 @@ git clone <your-fork-url> app
37
61
  cd app
38
62
  git remote add upstream <upstream-url>
39
63
  synopkgland init -t compose --upstream-fork --package MyApp \
40
- --upstream-image owner/app --health-path /actual-readiness --health-status 200 .
64
+ --upstream https://github.com/owner/app --publisher "Your team" \
65
+ --publisher-url https://example.com --upstream-image owner/app --health-path /actual-readiness --health-status 200 .
41
66
  ```
42
67
 
43
68
  Replace `owner/app` with the verified official repository. `init` does not guess
44
- its publisher or make network requests. Configure `synology/upstream-recipe.json`:
69
+ the official image publisher. Only attribution uses a best-effort GitHub profile lookup. Configure `synology/upstream-recipe.json`:
45
70
  `repository`, the actual Compose `service`, and `imageTag: tag` (exact upstream
46
71
  tag) or `version` (strip the leading `v` only if the official image uses that
47
72
  spelling). Adjust the App's port, storage and DSM login adapter before committing.
@@ -78,7 +103,9 @@ workaround with the framework ownership mechanism once available and verified.
78
103
  ## Native: official release binaries or a source build hook
79
104
 
80
105
  ```sh
81
- synopkgland init -t native-go --ui plain --upstream-fork --package MyApp .
106
+ synopkgland init -t native-go --ui plain --upstream-fork --package MyApp \
107
+ --upstream https://github.com/owner/app --publisher "Your team" \
108
+ --publisher-url https://example.com .
82
109
  ```
83
110
 
84
111
  Native Apps can stage official release binaries pinned by **SHA-256**, or compile
@@ -180,3 +207,11 @@ returns 503 even when the gateway is down. Availability is not identity attestat
180
207
  Declare only Container Manager-capable `arches`/INFO platforms; exclude unsupported
181
208
  NAS architectures and review models in each family. Actual missing-package wizard
182
209
  behavior and DSM's dependency not-ready behavior remain device acceptance items.
210
+
211
+ ## Release checklist
212
+
213
+ - Verify the original project/team or owner is the maintainer, its repository is
214
+ `metadata.maintainer_url` / `metadata.upstream`, and your publisher is the distributor
215
+ - Resolve attribution TODOs and review all `check --json` warnings
216
+ - Inspect the built SPK INFO attribution, preserve notices/source correspondence,
217
+ and verify the selected upstream release before publishing