@synopackageland/cli 0.6.1 → 0.7.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.
Files changed (578) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/README.md +81 -89
  3. package/dist/add.js +39 -16
  4. package/dist/build-spk/delegate-scope.d.ts +6 -0
  5. package/dist/build-spk/delegate-scope.js +34 -0
  6. package/dist/build-spk/dsm-arch.d.ts +7 -1
  7. package/dist/build-spk/dsm-arch.js +24 -28
  8. package/dist/build-spk/dsm-platforms.json +39 -0
  9. package/dist/build-spk/dsm-version.d.ts +11 -0
  10. package/dist/build-spk/dsm-version.js +17 -0
  11. package/dist/build-spk/gateway.d.ts +1 -1
  12. package/dist/build-spk/gateway.js +2 -2
  13. package/dist/build-spk/generator.js +30 -5
  14. package/dist/build-spk/notices.js +4 -1
  15. package/dist/build-spk/resource-worker.js +3 -0
  16. package/dist/build-spk/wizard-messages.d.ts +66 -0
  17. package/dist/build-spk/wizard-messages.js +75 -9
  18. package/dist/cli.js +15 -7
  19. package/dist/contract/schema-validate.js +8 -0
  20. package/dist/contract/validate.js +30 -0
  21. package/dist/deploy/deploy.d.ts +1 -1
  22. package/dist/deploy/deploy.js +8 -0
  23. package/dist/deploy/plan.js +1 -1
  24. package/dist/deploy/spk-version.js +1 -1
  25. package/dist/error-registry.js +43 -15
  26. package/dist/host/dsm-client.d.ts +8 -4
  27. package/dist/host/dsm-client.js +10 -4
  28. package/dist/host/types.d.ts +2 -1
  29. package/dist/info.js +18 -2
  30. package/dist/init.d.ts +3 -0
  31. package/dist/init.js +49 -18
  32. package/dist/package-metadata.d.ts +21 -0
  33. package/dist/package-metadata.js +101 -0
  34. package/dist/portal/alias.d.ts +25 -0
  35. package/dist/portal/alias.js +48 -0
  36. package/dist/portal/package.d.ts +33 -0
  37. package/dist/portal/package.js +116 -0
  38. package/dist/runtime/delegate-contract.d.ts +79 -0
  39. package/dist/runtime/delegate-contract.js +27 -0
  40. package/dist/runtime/executor-build.d.ts +1 -1
  41. package/dist/runtime/executor-build.js +5 -4
  42. package/dist/runtime/executor-inspect.d.ts +3 -0
  43. package/dist/runtime/executor-inspect.js +11 -5
  44. package/dist/runtime/executor-policy.d.ts +12 -0
  45. package/dist/runtime/executor-policy.js +43 -3
  46. package/dist/runtime/executor-scope-task.d.ts +4 -0
  47. package/dist/runtime/executor-scope-task.js +21 -0
  48. package/dist/runtime/executor-tasks.d.ts +2 -1
  49. package/dist/runtime/executor-tasks.js +4 -4
  50. package/dist/skill.js +1 -0
  51. package/dist/standalone/api-permissions.d.ts +10 -0
  52. package/dist/standalone/api-permissions.js +19 -2
  53. package/dist/standalone/background.d.ts +16 -0
  54. package/dist/standalone/background.js +69 -2
  55. package/dist/standalone/binary.d.ts +1 -1
  56. package/dist/standalone/binary.js +22 -8
  57. package/dist/standalone/build.d.ts +1 -1
  58. package/dist/standalone/build.js +38 -8
  59. package/dist/standalone/daemon.d.ts +1 -1
  60. package/dist/standalone/daemon.js +2 -1
  61. package/dist/standalone/dsm-window.d.ts +3 -1
  62. package/dist/standalone/dsm-window.js +72 -15
  63. package/dist/standalone/inspect-spk.d.ts +8 -0
  64. package/dist/standalone/inspect-spk.js +105 -26
  65. package/dist/standalone/lifecycle.js +86 -8
  66. package/dist/standalone/manifest.d.ts +13 -0
  67. package/dist/standalone/manifest.js +57 -8
  68. package/dist/standalone/native-authorization-window.d.ts +1 -1
  69. package/dist/standalone/native-authorization-window.js +2 -0
  70. package/dist/standalone/native-layout.d.ts +3 -0
  71. package/dist/standalone/native-layout.js +37 -5
  72. package/dist/standalone/portal-window.d.ts +21 -0
  73. package/dist/standalone/portal-window.js +155 -0
  74. package/dist/standalone/portal.d.ts +4 -0
  75. package/dist/standalone/portal.js +4 -0
  76. package/dist/standalone/resource.d.ts +5 -3
  77. package/dist/standalone/resource.js +6 -3
  78. package/dist/standalone/root-window.js +62 -15
  79. package/dist/standalone/spk.d.ts +1 -1
  80. package/dist/standalone/spk.js +25 -7
  81. package/dist/templates/_daemon/main_test.go +61 -8
  82. package/dist/templates/_executor/README.md +127 -16
  83. package/dist/templates/_executor/anchor.go +146 -0
  84. package/dist/templates/_executor/anchor_test.go +126 -0
  85. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  86. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  87. package/dist/templates/_executor/copy.go +64 -0
  88. package/dist/templates/_executor/copy_test.go +88 -0
  89. package/dist/templates/_executor/dsm.go +23 -10
  90. package/dist/templates/_executor/dsm_test.go +95 -1
  91. package/dist/templates/_executor/executor.go +260 -49
  92. package/dist/templates/_executor/executor_test.go +36 -6
  93. package/dist/templates/_executor/identity.go +27 -1
  94. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  95. package/dist/templates/_executor/identity_linux.go +82 -0
  96. package/dist/templates/_executor/identity_security_test.go +43 -0
  97. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  98. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  99. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  100. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  101. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  102. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  103. package/dist/templates/_executor/lifecycle_test.go +11 -0
  104. package/dist/templates/_executor/link_linux.go +28 -0
  105. package/dist/templates/_executor/long_running_test.go +530 -0
  106. package/dist/templates/_executor/main.go +72 -20
  107. package/dist/templates/_executor/move_linux.go +490 -0
  108. package/dist/templates/_executor/move_linux_test.go +460 -0
  109. package/dist/templates/_executor/move_validation.go +46 -0
  110. package/dist/templates/_executor/move_validation_test.go +187 -0
  111. package/dist/templates/_executor/operations.go +926 -0
  112. package/dist/templates/_executor/platform_linux.go +24 -173
  113. package/dist/templates/_executor/platform_linux_test.go +9 -65
  114. package/dist/templates/_executor/platform_other.go +30 -4
  115. package/dist/templates/_executor/process.go +3 -0
  116. package/dist/templates/_executor/records.go +242 -0
  117. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  118. package/dist/templates/_executor/scope_client.go +37 -0
  119. package/dist/templates/_executor/scope_contract.go +12 -0
  120. package/dist/templates/_executor/selected_scope.go +146 -0
  121. package/dist/templates/_executor/selected_scope_test.go +186 -0
  122. package/dist/templates/_executor/status_disabled_test.go +33 -0
  123. package/dist/templates/_executor/store.go +4 -3
  124. package/dist/templates/_executor/supervise.go +109 -0
  125. package/dist/templates/_executor/supervise_test.go +130 -0
  126. package/dist/templates/_executor/task_race_review_test.go +1 -1
  127. package/dist/templates/_executor/tasks.go +7 -8
  128. package/dist/templates/_executor/tasks_review_test.go +1 -1
  129. package/dist/templates/_executor/tasks_test.go +4 -4
  130. package/dist/templates/_executor/watch.go +8 -0
  131. package/dist/templates/_executor/watch_test.go +23 -2
  132. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  133. package/dist/templates/_shared/AGENTS.md +129 -0
  134. package/dist/templates/binary/dsm-package.yaml +24 -0
  135. package/dist/templates/binary/i18n/messages/de.json +4 -0
  136. package/dist/templates/binary/i18n/messages/en.json +4 -0
  137. package/dist/templates/binary/i18n/messages/es.json +4 -0
  138. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  139. package/dist/templates/binary/i18n/messages/it.json +4 -0
  140. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  141. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  142. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  143. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  144. package/dist/templates/binary/ui/app.js +19 -4
  145. package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
  146. package/dist/templates/native-authorization/i18n/de.json +1 -0
  147. package/dist/templates/native-authorization/i18n/en.json +1 -0
  148. package/dist/templates/native-authorization/i18n/es.json +2 -1
  149. package/dist/templates/native-authorization/i18n/fr.json +1 -0
  150. package/dist/templates/native-authorization/i18n/it.json +11 -10
  151. package/dist/templates/native-authorization/i18n/ja.json +1 -0
  152. package/dist/templates/native-authorization/i18n/ko.json +10 -9
  153. package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
  154. package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
  155. package/dist/templates/native-go/README.md +43 -10
  156. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  157. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  158. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  159. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  160. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  161. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  162. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  163. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  164. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  165. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  166. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  167. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  168. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  169. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  170. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  171. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  172. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  173. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  174. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  175. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  176. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  177. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  178. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  179. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  180. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  181. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  182. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  183. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  184. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  185. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  186. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  187. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  188. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  189. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  190. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  191. package/dist/templates/native-go/dsm-package.yaml +20 -1
  192. package/dist/templates/native-go/i18n/glossary.json +2 -1
  193. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  194. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  195. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  196. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  197. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  198. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  199. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  200. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  201. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  202. package/dist/templates/native-go/ui/app.css +10 -0
  203. package/dist/templates/native-go/ui/app.js +11 -1
  204. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  205. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  206. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  207. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  208. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  209. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  210. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  211. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  212. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  213. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  214. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  215. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  216. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  217. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  218. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  219. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  220. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  221. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  222. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  223. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  224. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  225. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  226. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  227. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  228. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  229. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  230. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  231. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  232. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  233. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  234. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  235. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  236. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  237. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  238. package/dist/templates/native-go-delegate/test.sh +1 -0
  239. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  240. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  241. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  242. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  243. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  244. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  245. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  246. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  247. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  248. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  249. package/dist/templates/native-go-vue/README.md +4 -0
  250. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  251. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  252. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  253. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  254. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  255. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  256. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  257. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  258. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  259. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  260. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  261. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  262. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  263. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  264. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  265. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  266. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  267. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  268. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  269. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  270. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  271. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  272. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  273. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  274. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  275. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  276. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  277. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  278. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  279. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  280. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  281. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  282. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  283. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  284. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  285. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  286. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  287. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  288. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  289. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  290. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  291. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  292. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  293. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  294. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  295. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  296. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  297. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  298. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  299. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  300. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  301. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  302. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  303. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  304. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  305. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  306. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  307. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  308. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  309. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  310. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  311. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  312. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  313. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  314. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  319. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  320. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  321. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  322. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  323. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  324. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  325. package/package.json +5 -1
  326. package/schema/package-metadata.schema.json +101 -0
  327. package/schema/synology-app.schema.json +112 -0
  328. package/skills/SKILL.md +106 -4
  329. package/skills/reference/create.md +101 -2
  330. package/skills/reference/deploy.md +2 -2
  331. package/skills/reference/files.md +113 -2
  332. package/skills/reference/identity.md +70 -1
  333. package/skills/reference/portal.md +17 -0
  334. package/skills/reference/ui-vue.md +12 -0
  335. package/templates/_daemon/main_test.go +61 -8
  336. package/templates/_executor/README.md +127 -16
  337. package/templates/_executor/anchor.go +146 -0
  338. package/templates/_executor/anchor_test.go +126 -0
  339. package/templates/_executor/apppriv_review_test.go +1 -1
  340. package/templates/_executor/cancel_contract_test.go +236 -0
  341. package/templates/_executor/copy.go +64 -0
  342. package/templates/_executor/copy_test.go +88 -0
  343. package/templates/_executor/dsm.go +23 -10
  344. package/templates/_executor/dsm_test.go +95 -1
  345. package/templates/_executor/executor.go +260 -49
  346. package/templates/_executor/executor_test.go +36 -6
  347. package/templates/_executor/identity.go +27 -1
  348. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  349. package/templates/_executor/identity_linux.go +82 -0
  350. package/templates/_executor/identity_security_test.go +43 -0
  351. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  352. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  353. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  354. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  355. package/templates/_executor/internal/taskscript/control.sh +15 -10
  356. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  357. package/templates/_executor/lifecycle_test.go +11 -0
  358. package/templates/_executor/link_linux.go +28 -0
  359. package/templates/_executor/long_running_test.go +530 -0
  360. package/templates/_executor/main.go +72 -20
  361. package/templates/_executor/move_linux.go +490 -0
  362. package/templates/_executor/move_linux_test.go +460 -0
  363. package/templates/_executor/move_validation.go +46 -0
  364. package/templates/_executor/move_validation_test.go +187 -0
  365. package/templates/_executor/operations.go +926 -0
  366. package/templates/_executor/platform_linux.go +24 -173
  367. package/templates/_executor/platform_linux_test.go +9 -65
  368. package/templates/_executor/platform_other.go +30 -4
  369. package/templates/_executor/process.go +3 -0
  370. package/templates/_executor/records.go +242 -0
  371. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  372. package/templates/_executor/scope_client.go +37 -0
  373. package/templates/_executor/scope_contract.go +12 -0
  374. package/templates/_executor/selected_scope.go +146 -0
  375. package/templates/_executor/selected_scope_test.go +186 -0
  376. package/templates/_executor/status_disabled_test.go +33 -0
  377. package/templates/_executor/store.go +4 -3
  378. package/templates/_executor/supervise.go +109 -0
  379. package/templates/_executor/supervise_test.go +130 -0
  380. package/templates/_executor/task_race_review_test.go +1 -1
  381. package/templates/_executor/tasks.go +7 -8
  382. package/templates/_executor/tasks_review_test.go +1 -1
  383. package/templates/_executor/tasks_test.go +4 -4
  384. package/templates/_executor/watch.go +8 -0
  385. package/templates/_executor/watch_test.go +23 -2
  386. package/templates/_executor/write_review_linux_test.go +4 -12
  387. package/templates/_shared/AGENTS.md +129 -0
  388. package/templates/binary/dsm-package.yaml +24 -0
  389. package/templates/binary/i18n/messages/de.json +4 -0
  390. package/templates/binary/i18n/messages/en.json +4 -0
  391. package/templates/binary/i18n/messages/es.json +4 -0
  392. package/templates/binary/i18n/messages/fr.json +4 -0
  393. package/templates/binary/i18n/messages/it.json +4 -0
  394. package/templates/binary/i18n/messages/ja.json +4 -0
  395. package/templates/binary/i18n/messages/ko.json +4 -0
  396. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  397. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  398. package/templates/binary/ui/app.js +19 -4
  399. package/templates/native-authorization/AuthorizationGate.vue +5 -3
  400. package/templates/native-authorization/i18n/de.json +1 -0
  401. package/templates/native-authorization/i18n/en.json +1 -0
  402. package/templates/native-authorization/i18n/es.json +2 -1
  403. package/templates/native-authorization/i18n/fr.json +1 -0
  404. package/templates/native-authorization/i18n/it.json +11 -10
  405. package/templates/native-authorization/i18n/ja.json +1 -0
  406. package/templates/native-authorization/i18n/ko.json +10 -9
  407. package/templates/native-authorization/i18n/zh-CN.json +6 -5
  408. package/templates/native-authorization/i18n/zh-TW.json +6 -5
  409. package/templates/native-go/README.md +43 -10
  410. package/templates/native-go/backend/cmd/app/main.go +27 -7
  411. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  412. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  413. package/templates/native-go/backend/internal/api/registry.go +11 -0
  414. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  415. package/templates/native-go/backend/internal/api/server.go +18 -5
  416. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  417. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  418. package/templates/native-go/backend/internal/app/portal.go +30 -0
  419. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  420. package/templates/native-go/backend/internal/app/routes.go +8 -2
  421. package/templates/native-go/backend/internal/background/runner.go +1 -1
  422. package/templates/native-go/backend/internal/background/store.go +16 -14
  423. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  424. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  425. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  426. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  427. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  428. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  429. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  430. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  431. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  432. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  433. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  434. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  435. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  436. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  437. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  438. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  439. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  440. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  441. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  442. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  443. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  444. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  445. package/templates/native-go/dsm-package.yaml +20 -1
  446. package/templates/native-go/i18n/glossary.json +2 -1
  447. package/templates/native-go/i18n/messages/de.json +1 -0
  448. package/templates/native-go/i18n/messages/en.json +1 -0
  449. package/templates/native-go/i18n/messages/es.json +1 -0
  450. package/templates/native-go/i18n/messages/fr.json +1 -0
  451. package/templates/native-go/i18n/messages/it.json +1 -0
  452. package/templates/native-go/i18n/messages/ja.json +1 -0
  453. package/templates/native-go/i18n/messages/ko.json +1 -0
  454. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  455. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  456. package/templates/native-go/ui/app.css +10 -0
  457. package/templates/native-go/ui/app.js +11 -1
  458. package/templates/native-go-delegate/DELEGATE.md +126 -5
  459. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  460. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  461. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  462. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  463. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  464. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  465. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  466. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  467. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  468. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  469. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  470. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  471. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  472. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  473. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  474. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  475. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  476. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  477. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  478. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  479. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  480. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  481. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  482. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  483. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  484. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  485. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  486. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  487. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  488. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  489. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  490. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  491. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  492. package/templates/native-go-delegate/test.sh +1 -0
  493. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  494. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  495. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  496. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  497. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  498. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  499. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  500. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  501. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  502. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  503. package/templates/native-go-vue/README.md +4 -0
  504. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  505. package/templates/native-go-vue/frontend/package.json +1 -1
  506. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  507. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  508. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  509. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  510. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  511. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  512. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  513. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  514. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  515. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  516. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  517. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  518. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  519. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  520. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  521. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  522. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  523. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  524. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  525. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  526. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  527. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  528. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  529. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  530. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  531. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  532. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  533. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  534. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  535. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  536. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  537. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  538. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  539. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  540. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  541. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  542. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  543. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  544. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  545. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  546. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  547. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  548. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  549. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  550. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  551. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  552. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  553. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  554. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  555. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  556. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  557. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  558. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  559. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  560. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  561. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  562. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  563. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  564. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  565. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  566. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  567. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  568. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  569. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  570. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  571. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  572. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  573. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  574. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  575. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  576. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  577. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  578. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -0,0 +1,130 @@
1
+ package main
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "fmt"
7
+ "os"
8
+ "os/exec"
9
+ "reflect"
10
+ "testing"
11
+ "time"
12
+ )
13
+
14
+ type superviseFixture struct {
15
+ scripts []string
16
+ revived []bool
17
+ delays []time.Duration
18
+ present bool
19
+ clock time.Time
20
+ runFor []time.Duration
21
+ }
22
+
23
+ // Each run executes a real child so exit status and signal death come from the
24
+ // kernel, exactly as the supervisor sees serve.
25
+ func (f *superviseFixture) run(ctx context.Context, revived bool) error {
26
+ i := len(f.revived)
27
+ f.revived = append(f.revived, revived)
28
+ if i < len(f.runFor) {
29
+ f.clock = f.clock.Add(f.runFor[i])
30
+ }
31
+ script := "exit 0"
32
+ if i < len(f.scripts) {
33
+ script = f.scripts[i]
34
+ }
35
+ return exec.CommandContext(ctx, "/bin/sh", "-c", script).Run()
36
+ }
37
+ func (f *superviseFixture) sleep(_ context.Context, d time.Duration) bool {
38
+ f.delays = append(f.delays, d)
39
+ return true
40
+ }
41
+ func (f *superviseFixture) supervise(ctx context.Context) error {
42
+ return supervise(ctx, f.run, func() bool { return f.present }, f.sleep, func() time.Time { return f.clock }, func(string) {})
43
+ }
44
+
45
+ func TestSuperviseRestartsAbnormalServeUntilCleanExit(t *testing.T) {
46
+ f := &superviseFixture{present: true, scripts: []string{"kill -KILL $$", "exit 1", "exit 0"}}
47
+ if err := f.supervise(context.Background()); err != nil {
48
+ t.Fatal(err)
49
+ }
50
+ if want := []bool{false, true, true}; !reflect.DeepEqual(f.revived, want) {
51
+ t.Fatalf("revived %v, want %v", f.revived, want)
52
+ }
53
+ if want := []time.Duration{time.Second, 2 * time.Second}; !reflect.DeepEqual(f.delays, want) {
54
+ t.Fatalf("delays %v, want %v", f.delays, want)
55
+ }
56
+ }
57
+
58
+ func TestSuperviseLeavesWhenAnotherMonitorOwnsThePidfile(t *testing.T) {
59
+ f := &superviseFixture{present: true, scripts: []string{"exit 3", "exit 0"}}
60
+ if err := f.supervise(context.Background()); err != nil {
61
+ t.Fatal(err)
62
+ }
63
+ if len(f.revived) != 1 || len(f.delays) != 0 {
64
+ t.Fatalf("runs %v delays %v", f.revived, f.delays)
65
+ }
66
+ }
67
+
68
+ func TestSuperviseStopsOnceRuntimeIsPurged(t *testing.T) {
69
+ f := &superviseFixture{present: false, scripts: []string{"kill -KILL $$", "exit 0"}}
70
+ if err := f.supervise(context.Background()); err != nil {
71
+ t.Fatal(err)
72
+ }
73
+ if len(f.revived) != 1 || len(f.delays) != 0 {
74
+ t.Fatalf("runs %v delays %v", f.revived, f.delays)
75
+ }
76
+ }
77
+
78
+ func TestSuperviseBacksOffToCapAndResetsAfterStableRun(t *testing.T) {
79
+ scripts := make([]string, 10)
80
+ for i := range scripts {
81
+ scripts[i] = "exit 2"
82
+ }
83
+ scripts = append(scripts, "exit 0")
84
+ runFor := make([]time.Duration, len(scripts))
85
+ runFor[8] = time.Minute
86
+ f := &superviseFixture{present: true, scripts: scripts, runFor: runFor}
87
+ if err := f.supervise(context.Background()); err != nil {
88
+ t.Fatal(err)
89
+ }
90
+ s := time.Second
91
+ want := []time.Duration{s, 2 * s, 4 * s, 8 * s, 16 * s, 32 * s, 60 * s, 60 * s, s, 2 * s}
92
+ if !reflect.DeepEqual(f.delays, want) {
93
+ t.Fatalf("delays %v, want %v", f.delays, want)
94
+ }
95
+ }
96
+
97
+ func TestSuperviseTerminationEndsWithoutRestart(t *testing.T) {
98
+ ctx, cancel := context.WithCancel(context.Background())
99
+ f := &superviseFixture{present: true, scripts: []string{"sleep 30", "exit 0"}}
100
+ time.AfterFunc(100*time.Millisecond, cancel)
101
+ if err := f.supervise(ctx); err != nil {
102
+ t.Fatal(err)
103
+ }
104
+ if len(f.revived) != 1 || len(f.delays) != 0 {
105
+ t.Fatalf("runs %v delays %v", f.revived, f.delays)
106
+ }
107
+ }
108
+
109
+ func TestRootStartLaunchesSupervisorThatRevivesServe(t *testing.T) {
110
+ if os.Geteuid() == 0 {
111
+ t.Skip("checks the non-root refusal")
112
+ }
113
+ if err := run([]string{"supervise", "--package", "TestApp"}); err == nil || err.Error() != "root required" {
114
+ t.Fatalf("supervise mode: %v", err)
115
+ }
116
+ if err := run([]string{"control", "start", "--package", "TestApp", "--revived"}); err == nil || err.Error() != "unexpected revive" {
117
+ t.Fatalf("revive outside serve: %v", err)
118
+ }
119
+ if got := supervisorCommand("/x/runtime/executor", "TestApp").Args; !reflect.DeepEqual(got, []string{"/x/runtime/executor", "supervise", "--package", "TestApp"}) {
120
+ t.Fatal(got)
121
+ }
122
+ for revived, want := range map[bool][]string{false: {"serve", "--package", "TestApp"}, true: {"serve", "--package", "TestApp", "--revived"}} {
123
+ if got := serveArgs("TestApp", revived); !reflect.DeepEqual(got, want) {
124
+ t.Fatal(got)
125
+ }
126
+ }
127
+ if exitStatus(fmt.Errorf("serve: %w", errAlreadyRunning)) != exitAlreadyRunning || exitStatus(errors.New("other")) != 1 {
128
+ t.Fatal("exit status")
129
+ }
130
+ }
@@ -24,7 +24,7 @@ func (s *reviewEditDuringDelete) Exec(ctx context.Context, path string, args []s
24
24
  }
25
25
  func TestReviewPurgeAcceptedAdminRaceLogsRemainingTasksWithoutRetry(t *testing.T) {
26
26
  cmd := &reviewEditDuringDelete{}
27
- inv := taskInventory{Package: "TestApp", ExecutorDigest: strings.Repeat("a", 64), PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: canonicalTaskDigest(t, "boot")}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: canonicalTaskDigest(t, "control")}}}
27
+ inv := taskInventory{Package: "TestApp", PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: canonicalTaskDigest(t, "boot")}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: canonicalTaskDigest(t, "control")}}}
28
28
  logs := []string{}
29
29
  purgeTasks(context.Background(), &liveDSM{Command: cmd}, "TestApp", inv, func(s string) { logs = append(logs, s) })
30
30
  if len(cmd.deleted) != 1 {
@@ -18,15 +18,14 @@ type approvedTask struct {
18
18
  ScriptDigest string `json:"script_digest"`
19
19
  }
20
20
  type taskInventory struct {
21
- Package string `json:"package"`
22
- ExecutorDigest string `json:"executor_digest"`
23
- PolicyDigest string `json:"policy_digest"`
24
- Tasks []approvedTask `json:"tasks"`
21
+ Package string `json:"package"`
22
+ PolicyDigest string `json:"policy_digest"`
23
+ Tasks []approvedTask `json:"tasks"`
25
24
  }
26
25
 
27
26
  func hashBytes(b []byte) string { h := sha256.Sum256(b); return hex.EncodeToString(h[:]) }
28
27
  func validInventory(pkg string, inventory taskInventory) bool {
29
- if inventory.Package != pkg || len(inventory.Tasks) != 2 || !idRE.MatchString(inventory.ExecutorDigest) || !idRE.MatchString(inventory.PolicyDigest) {
28
+ if inventory.Package != pkg || len(inventory.Tasks) != 2 || !idRE.MatchString(inventory.PolicyDigest) {
30
29
  return false
31
30
  }
32
31
  seen := map[string]bool{}
@@ -150,11 +149,11 @@ func snapshotTasks(ctx context.Context, d *liveDSM, pkg, ed, pd, setupPath, runt
150
149
  if strictJSON(b, &setup) != nil || setup.Package != pkg || setup.ExecutorDigest != ed || setup.PolicyDigest != pd || setup.Implementation != "executor" {
151
150
  return errors.New("setup pins")
152
151
  }
153
- generated, err := taskscript.Generate(pkg, ed, pd)
152
+ generated, err := taskscript.Generate(pkg, pd)
154
153
  if err != nil || len(setup.Tasks) != len(generated) {
155
154
  return errors.New("setup task inventory")
156
155
  }
157
- inv := taskInventory{Package: pkg, ExecutorDigest: ed, PolicyDigest: pd}
156
+ inv := taskInventory{Package: pkg, PolicyDigest: pd}
158
157
  for i, task := range generated {
159
158
  hint := setup.Tasks[i]
160
159
  if hint.Kind != task.Kind || hint.Name != task.Name || hint.Header != task.Header || hint.Script != task.Script {
@@ -214,7 +213,7 @@ func purgeTasks(ctx context.Context, d *liveDSM, pkg string, inv taskInventory,
214
213
  log("task cleanup skipped: invalid protected inventory")
215
214
  return
216
215
  }
217
- generated, err := taskscript.Generate(pkg, inv.ExecutorDigest, inv.PolicyDigest)
216
+ generated, err := taskscript.Generate(pkg, inv.PolicyDigest)
218
217
  if err != nil {
219
218
  log("task cleanup skipped: invalid protected pins")
220
219
  return
@@ -35,7 +35,7 @@ func TestReviewSnapshotRejectsUnrelatedSameNamedRootTasks(t *testing.T) {
35
35
  func TestReviewPurgeRejectsUnrelatedSameNamedRootTasks(t *testing.T) {
36
36
  script := "#!/bin/sh\n# synopkgland executor boot\n# " + strings.Repeat("a", 64) + strings.Repeat("b", 64) + "\necho unrelated-backup-task\n"
37
37
  cmd := &schedulerFixture{script: script}
38
- inv := taskInventory{Package: "TestApp", ExecutorDigest: strings.Repeat("a", 64), PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: hashBytes([]byte(script))}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: hashBytes([]byte(script))}}}
38
+ inv := taskInventory{Package: "TestApp", PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: hashBytes([]byte(script))}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: hashBytes([]byte(script))}}}
39
39
  purgeTasks(context.Background(), &liveDSM{Command: cmd}, "TestApp", inv, func(string) {})
40
40
  if len(cmd.deleted) != 0 {
41
41
  t.Fatal("deleted unrelated tasks", cmd.deleted)
@@ -31,7 +31,7 @@ func (s *schedulerFixture) Exec(_ context.Context, _ string, args []string) ([]b
31
31
  if s.foreignOwner {
32
32
  owner = "other"
33
33
  }
34
- generated, _ := taskscript.Generate("TestApp", strings.Repeat("a", 64), strings.Repeat("b", 64))
34
+ generated, _ := taskscript.Generate("TestApp", strings.Repeat("b", 64))
35
35
  script := generated[1].Script
36
36
  if api == "SYNO.Core.EventScheduler" {
37
37
  script = generated[0].Script
@@ -66,7 +66,7 @@ func TestPurgeDeletesOnlyExactOwnRootTasksAndProtectedState(t *testing.T) {
66
66
  for _, mode := range []string{"exact", "script changed", "owner changed", "duplicate control", "foreign name"} {
67
67
  t.Run(mode, func(t *testing.T) {
68
68
  cmd := &schedulerFixture{changed: mode == "script changed", foreignOwner: mode == "owner changed", duplicate: mode == "duplicate control"}
69
- inventory := taskInventory{Package: "TestApp", ExecutorDigest: strings.Repeat("a", 64), PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: canonicalTaskDigest(t, "boot")}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: canonicalTaskDigest(t, "control")}}}
69
+ inventory := taskInventory{Package: "TestApp", PolicyDigest: strings.Repeat("b", 64), Tasks: []approvedTask{{Kind: "boot", Name: "TestApp executor boot", ScriptDigest: canonicalTaskDigest(t, "boot")}, {Kind: "control", Name: "TestApp executor control", ScriptDigest: canonicalTaskDigest(t, "control")}}}
70
70
  if mode == "foreign name" {
71
71
  inventory.Tasks[0].Name = "OtherApp executor boot"
72
72
  }
@@ -101,7 +101,7 @@ func TestSnapshotRequiresBothExactSchedulerDefinitionsBeforeWritingInventory(t *
101
101
  ed := strings.Repeat("a", 64)
102
102
  pd := strings.Repeat("b", 64)
103
103
  tasks := []map[string]string{}
104
- generated, err := taskscript.Generate("TestApp", ed, pd)
104
+ generated, err := taskscript.Generate("TestApp", pd)
105
105
  if err != nil {
106
106
  t.Fatal(err)
107
107
  }
@@ -152,7 +152,7 @@ func (s *snapshotFixture) Exec(ctx context.Context, path string, args []string)
152
152
 
153
153
  func canonicalTaskDigest(t *testing.T, kind string) string {
154
154
  t.Helper()
155
- tasks, err := taskscript.Generate("TestApp", strings.Repeat("a", 64), strings.Repeat("b", 64))
155
+ tasks, err := taskscript.Generate("TestApp", strings.Repeat("b", 64))
156
156
  if err != nil {
157
157
  t.Fatal(err)
158
158
  }
@@ -32,6 +32,14 @@ func newLifecycleMonitor(pkg, packageDir, control string, log func(string)) *lif
32
32
  return m
33
33
  }
34
34
 
35
+ // A supervisor restart carries no new approval, so a stop already in the
36
+ // mailbox stays in force.
37
+ func newRevivedLifecycleMonitor(pkg, packageDir, control string, log func(string)) *lifecycleMonitor {
38
+ m := newLifecycleMonitor(pkg, packageDir, control, log)
39
+ m.initialStop = nil
40
+ return m
41
+ }
42
+
35
43
  // DSM's var link is expected. Resolve its parent, then reject final symlinks,
36
44
  // FIFOs and oversized input. Linux openRegular pins all resolved components.
37
45
  func readLifecycleFile(path string, limit int) ([]byte, os.FileInfo, error) {
@@ -209,7 +209,8 @@ func TestLifecycleWatchIgnoresInvalidControlAndResumesAfterOldStop(t *testing.T)
209
209
  os.WriteFile(control, []byte("stop\n"), 0600)
210
210
  var mu sync.Mutex
211
211
  logs := 0
212
- monitor := newLifecycleMonitor("TestApp", pkg, control, func(string) { mu.Lock(); logs++; mu.Unlock() })
212
+ observed := make(chan struct{}, 32)
213
+ monitor := newLifecycleMonitor("TestApp", pkg, control, func(string) { mu.Lock(); logs++; mu.Unlock(); observed <- struct{}{} })
213
214
  ctx, cancel := context.WithTimeout(context.Background(), time.Second)
214
215
  defer cancel()
215
216
  result := make(chan string, 1)
@@ -225,7 +226,13 @@ func TestLifecycleWatchIgnoresInvalidControlAndResumesAfterOldStop(t *testing.T)
225
226
  checkRunning()
226
227
  for _, value := range []string{"STOP", " purge", "purge\n\n", "purge;id", "wrong"} {
227
228
  os.WriteFile(control, []byte(value), 0600)
228
- checkRunning()
229
+ select {
230
+ case <-observed:
231
+ case got := <-result:
232
+ t.Fatalf("unexpected action %q", got)
233
+ case <-ctx.Done():
234
+ t.Fatal("invalid request not observed")
235
+ }
229
236
  }
230
237
  mu.Lock()
231
238
  count := logs
@@ -268,3 +275,17 @@ func TestLifecycleMonitorReadsStartAfterStopAndKeepsCheckingPurge(t *testing.T)
268
275
  }
269
276
  }
270
277
  }
278
+
279
+ // A supervisor restart is not a new administrator approval: the stop already in
280
+ // the mailbox keeps the revived monitor paused.
281
+ func TestRevivedMonitorHonorsStopAlreadyInMailbox(t *testing.T) {
282
+ pkg := t.TempDir()
283
+ os.WriteFile(filepath.Join(pkg, "INFO"), []byte("package=\"TestApp\"\n"), 0600)
284
+ control := filepath.Join(pkg, "control")
285
+ os.WriteFile(control, []byte("stop\n"), 0600)
286
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
287
+ defer cancel()
288
+ if got := newRevivedLifecycleMonitor("TestApp", pkg, control, func(string) {}).watch(ctx, time.Millisecond); got != "stop" || ctx.Err() != nil {
289
+ t.Fatal("revived monitor ignored stop", got, ctx.Err())
290
+ }
291
+ }
@@ -11,15 +11,6 @@ import (
11
11
  "time"
12
12
  )
13
13
 
14
- func TestReviewCopyChild(t *testing.T) {
15
- if os.Getenv("P2_REVIEW_COPY_CHILD") != "1" {
16
- t.Skip("child only")
17
- }
18
- if err := runWriteChild(os.Getenv("P2_REVIEW_CLEANUP_CHILD") == "1"); err != nil {
19
- os.Exit(2)
20
- }
21
- os.Exit(0)
22
- }
23
14
  func TestReviewCanceledWriteDoesNotLeavePartialDestination(t *testing.T) {
24
15
  if os.Geteuid() != 0 {
25
16
  t.Skip("requires disposable local Linux root runner")
@@ -39,7 +30,7 @@ func TestReviewCanceledWriteDoesNotLeavePartialDestination(t *testing.T) {
39
30
  if err != nil {
40
31
  t.Fatal(err)
41
32
  }
42
- if err = src.Truncate(1 << 40); err != nil {
33
+ if err = src.Truncate(8 << 30); err != nil {
43
34
  src.Close()
44
35
  t.Skip(err)
45
36
  }
@@ -49,7 +40,7 @@ func TestReviewCanceledWriteDoesNotLeavePartialDestination(t *testing.T) {
49
40
  t.Fatal(err)
50
41
  }
51
42
  launcher := filepath.Join(base, "child")
52
- body := "#!/bin/sh\nexport P2_REVIEW_COPY_CHILD=1\n[ \"$1\" != cleanup-child ] || export P2_REVIEW_CLEANUP_CHILD=1\nexec " + strconv.Quote(binary) + " -test.run=^TestReviewCopyChild$\n"
43
+ body := "#!/bin/sh\nexport W3_MOVE_CHILD=\"$1\"\nexec " + strconv.Quote(binary) + " -test.run=^TestMoveChild$ 2>" + strconv.Quote(filepath.Join(base, "error")) + "\n"
53
44
  os.WriteFile(launcher, []byte(body), 0700)
54
45
  ctx, cancel := context.WithCancel(context.Background())
55
46
  defer cancel()
@@ -74,7 +65,8 @@ func TestReviewCanceledWriteDoesNotLeavePartialDestination(t *testing.T) {
74
65
  }
75
66
  select {
76
67
  case err := <-done:
77
- t.Fatalf("child ended before write: %v", err)
68
+ b, _ := os.ReadFile(filepath.Join(base, "error"))
69
+ t.Fatalf("child ended before write: %v %s", err, b)
78
70
  case <-deadline:
79
71
  t.Fatal("write did not start")
80
72
  default:
@@ -15,3 +15,132 @@ From the project root, run `synopkgland check --json`,
15
15
  `synopkgland build --json`, then `synopkgland inspect <spk> --json` on the emitted SPK.
16
16
  For Vue, first run `npm ci`, `npm test` and `npm run build` in frontend.
17
17
  Do not connect to or change a NAS without explicit task authorization.
18
+
19
+ ## Who may use the app (`entry`)
20
+
21
+ Omit `entry` to keep legacy access. `entry: admin-only` admits DSM administrators
22
+ only. `entry: app-privilege` also admits accounts that Control Panel >
23
+ Application Privileges allows for `SYNO.SDS.<package>.Instance`; DSM's install
24
+ default allows administrators only, so nobody else enters until an admin grants
25
+ access. One CGI check covers every route; failures refuse; changes apply within
26
+ 30 seconds. Never re-implement AppPriv rules in the App.
27
+ Mark routes ordinary users call `authenticated` in permissions.json, keep whole
28
+ admin routes at the default, guard global settings inside shared routes with
29
+ `api.RequireAdmin(caller)`, and hide admin controls with `ctx.user?.isAdmin`
30
+ (UI only, never the authority). Example: users read the greeting; only admins
31
+ call `greeting.set`.
32
+
33
+ ## DSM Application Portal (`portal`)
34
+
35
+ `synopkgland add portal` (or `portal: {}` / `portal: {alias: my-app}` in
36
+ dsm-package.yaml; Compose: `integration.web.portal` for a DSM-window app behind
37
+ the auth gateway) registers `https://<nas>/<alias>/`, which opens the app after
38
+ DSM sign-in. Read `synopkgland skill show portal` before changing it.
39
+ The install wizard only proposes the alias; an administrator activates it from
40
+ the app's portal panel (`ctx.portal`, DSM's own `SYNO.Core.AppPortal` in the
41
+ admin's session) or Control Panel > Login Portal > Applications. Ordinary users
42
+ get the link from the backend's read-only `portal.status`, else "ask an
43
+ administrator". Never write `AppPortal.json` or `@appconf/<package>`, never add
44
+ a backend route that sets the alias, and never relay QuickConnect responses or
45
+ build QuickConnect links. A portal needs os_min_ver 7.3.2-86009 or later (DSM
46
+ 7.1.1 refuses it with error 320; 7.2 is untested) and `add portal` raises it;
47
+ beyond that, support is detected (SYNO.API.Info), not version-gated.
48
+
49
+ ## Permanent destination integration
50
+
51
+ `init --with delegate` / `add delegate` default to `operations: [files.moveFromStaging]`,
52
+ `destinations: [save]`, `lifetime: until-revoked`, `scope: install-selected`. Existing legacy
53
+ TTL declarations retain their 1–3600 second service / 30-day wire ceiling and
54
+ 64 MiB / 60-second URL example. Do not apply those limits to the new move API.
55
+ Delegate can coexist with daemon; ordinary standalone jobs cannot. Preserve
56
+ App business route floors. Binary/compose remain unsupported.
57
+
58
+ Use File Station `/home[/...]`, never `/homes/<user>`. The SDK destination picker
59
+ selection itself grants access after fresh DSM session/UID/write verification,
60
+ without a second consent dialog. Ordinary and administrator subjects use their
61
+ own nonzero UID/current groups, never root. An online App can create an in-scope
62
+ grant using a valid session; there is no trusted external consent or notification.
63
+
64
+ Use the default `createBackgroundSaveFacade(ctx)` / CapabilityPanels management
65
+ page. `delegate.status` exposes verified `disabled` and manifest mode; missing
66
+ protected evidence must not imply enabled. `delegate.operations.list` accepts
67
+ only `{}`, uses the backend move mirrors, and returns the caller's username+UID
68
+ operations only, including for admins. Mirrors are not completion receipts:
69
+ query original `delegate.operations.get` and show Saved only for committed with
70
+ a receipt. Cancel by the same reference; revoke by grant ID. Never resubmit
71
+ uncertain/interrupted work. Admin disable is protected and revokes grants;
72
+ enable/mailbox start does not resurrect them. Keep IDs, never session credentials.
73
+
74
+ App-owned daemon completion and scheduled CSV export call configured Go
75
+ `Service.MoveFromStaging` with `delegation_id`, stable `job_id`, private relative
76
+ `staging`, `name`, `size`. No owner/UID selector. Bounds: 16 GiB/file, queued16,
77
+ active1/package, 24-hour attempt, 5-second revalidation, 10 MiB operation records,
78
+ 50 MiB audit. Scope is a usage restriction, not hard subtree confinement.
79
+ Local production-executor fixtures use fake DSM/peer/bulk writer; do not claim
80
+ NAS ACL, actual writer UID, logout/reboot or full lifecycle acceptance from them.
81
+ Gopeed product wiring remains a separate task.
82
+
83
+ Admission recovery (per job): on `EXECUTOR_UNAVAILABLE`, `EXECUTOR_TIMEOUT`,
84
+ `EXECUTOR_PROTOCOL`, `canceled`, `EXECUTOR_PAUSED`, `queue-full`,
85
+ `STORAGE_ERROR`, `ADMISSION_PENDING`, `jobs_busy` or `jobs_unavailable` (the
86
+ package journal was busy or failed transiently; a caller deadline or cancel
87
+ spent waiting for it is `canceled`), call `MoveFromStaging` again later with
88
+ the same exact params. It queries the original reference first and re-sends
89
+ admission only when the executor does not know it and the pinned source is
90
+ unchanged; executor tuple deduplication keeps one operation, so a lost reply or a
91
+ racing caller never copies twice. At most 10 possibly-delivered sends, 2 s
92
+ backoff doubling to 5 min; the worker sweep does the same. Terminal, do not
93
+ retry: `APP_DENIED`, `DELEGATION_DENIED`, `operation-conflict`, `disabled`,
94
+ `SCOPE_DENIED` and other executor refusals, `STAGING_CHANGED`,
95
+ `ADMISSION_EXHAUSTED`, `spool_unsafe` and `journal_corrupt` (repair the package
96
+ spool first), and `operation-not-found` for an accepted operation.
97
+ Never change job/grant IDs, delete mirrors or call the socket directly to retry.
98
+
99
+ Cancellation (per job): `CancelOperation` / `delegate.operations.cancel` with the
100
+ original ref is idempotent; repeat it with the same ref after any retryable
101
+ error or a lost reply. A reply `queued`→`canceled` is final at once. A copy in
102
+ flight answers `copying` with code `CANCEL_REQUESTED`: the cancel is accepted and
103
+ durable, not finished; poll `OperationStatus` on the same ref until terminal
104
+ (repeating the cancel is harmless). `canceled` (`OPERATION_CANCELED`) guarantees the writer
105
+ stopped, its part file is removed, nothing was published and staging is kept; it
106
+ stays canceled across executor crash, package stop/start and logout. `committed`
107
+ means publication won the race: the file stays (cancel never removes it) and only
108
+ the app record may be dropped. `uncertain` means a publish attempt failed before
109
+ the cancel; keep querying. A cancel of a ref the executor has not admitted yet
110
+ (reserved/sent mirror) returns `canceled` and leaves a tombstone, so a late or
111
+ recovered admission of that ref returns `canceled` and never copies; a producer
112
+ cancel is also written to the mirror first, so `MoveFromStaging` and the worker
113
+ sweep forward the cancel instead of admitting it.
114
+
115
+
116
+ ### Install-selected scope and the destination API
117
+
118
+ - The DSM install wizard dynamically lists local shares (`SYNO.Core.Share list`
119
+ via `api_store`) and offers home. It saves only a package-owned pre-selection
120
+ in `var/delegate-install.json`; never trust that file for authority.
121
+ - The existing administrator PasswordConfirm form displays this pre-selection
122
+ at first enable. `ctx.executor.authorize(password, selectedScope)` confirms it
123
+ through a pinned, exact-readback one-time root task. Settings changes use the
124
+ same form/password path; no App CGI route may set protected scope directly.
125
+ - The executor trusts `/usr/local/etc/synopkgland/<pkg>/delegations/selected-scope.record`
126
+ only. Its scope/generation are runtime protected state; shipped policy pins
127
+ lock the selection mode and operations. Changes revoke old grants and cancel
128
+ pending writes. Change-away-and-back never revives an old grant.
129
+ - Follow `selection` from verified `delegate.status` / `sdk.destination(key)`.
130
+ If the draft differs, display the difference and follow the protected value.
131
+ Home and fixed `shared:<name>` manifests stay compatible.
132
+ - Frontend: `const sdk = createBackgroundSaveFacade(ctx); const current = await
133
+ sdk.destination('save')` returns typed `{key, selection, destination, status}`.
134
+ `selection` contains `{scope, root, generation}`; `destination` is this caller's
135
+ grant or null. Handle active/missing/reselection-required/needs-scope-confirmation/disabled.
136
+ - Go: `Service.Destination(ctx, caller, request, key)` verifies the fresh DSM
137
+ session and username+UID. Capture its active grant ID into the App job. The
138
+ offline producer uses existing `Service.MoveFromStaging`; no user/UID selector,
139
+ cookie, token, or browser path cache. Executor revalidates at use/publication.
140
+ - The generated Background saves page includes a working **Save example file**
141
+ (`sdk.exportExample({key, job_id})`, `delegate.example.export`). Go
142
+ `ExportExample` creates a private JSON file, calls `MoveFromStaging` once, and
143
+ repeats the original request on repeated IDs. Review `example.go` to
144
+ adapt a real producer; show Saved only from a protected committed receipt.
145
+ - Wizard support is measured on vt-nas DSM 7.4 via a session-only SSH local
146
+ forward. This is not proof of other DSM releases or full writer/reboot behavior.
@@ -6,7 +6,27 @@ build: 1
6
6
  displayname: __DISPLAY_NAME__
7
7
  description: A DSM package with a prebuilt binary CGI and DSM window UI.
8
8
  maintainer: Your Name
9
+ # Optional INFO/website metadata. Omit fields you do not need.
10
+ # metadata:
11
+ # contact_email: you@example.com
12
+ # support_email: support@example.com
13
+ # homepage: https://example.com
14
+ # source_url: https://github.com/you/your-app
15
+ # issues_url: https://github.com/you/your-app/issues
16
+ # license: MIT
17
+ # long_description:
18
+ # en: |-
19
+ # Explain what the app does and how to use it.
20
+ # zh-TW: Translated description.
21
+ # changelog: # text, or per-language like long_description
22
+ # en: |-
23
+ # - Most important change first. Omit to use Release notes.
24
+ # zh-TW: |-
25
+ # - Translated changes.
26
+ # screenshots: [https://example.com/screenshot.png]
9
27
  os_min_ver: "7.1-42661"
28
+ # One SPK per architecture. For armv7, add it here and a static 32-bit ARM file
29
+ # under files (e.g. CGO_ENABLED=0 GOARCH=arm GOARM=7 go build).
10
30
  arches: [x86_64, aarch64]
11
31
  backend:
12
32
  kind: binary
@@ -14,6 +34,10 @@ backend:
14
34
  x86_64: prebuilt/api-x86_64
15
35
  aarch64: prebuilt/api-aarch64
16
36
  ui: {dir: ui}
37
+ # UI languages: i18n/messages/<locale>.json for en, zh-TW, zh-CN, ja, ko, de,
38
+ # fr, es and it (DSM's other languages fall back to English); ctx.t(key) reads them.
39
+ i18n:
40
+ messages: i18n/messages
17
41
  # init compiles example/main.go for convenience. Replace prebuilt/api-* with
18
42
  # your application's static Linux ELF files; the CGI owns authentication.
19
43
  # daemon may optionally declare a separately packaged foreground service.
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "Ihr vorab kompiliertes CGI ist in dieser DSM-Anwendung enthalten.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "Your prebuilt CGI is packaged in this DSM application.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "Su CGI precompilado está empaquetado en esta aplicación de DSM.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "Votre CGI précompilé est intégré à cette application DSM.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "Il CGI precompilato è incluso in questa applicazione DSM.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "事前にビルドした CGI がこの DSM アプリケーションにパッケージ化されています。",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "미리 빌드한 CGI가 이 DSM 응용 프로그램에 패키징되어 있습니다.",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "预先构建的 CGI 已打包在此 DSM 应用程序中。",
3
+ "app.title": "Binary App"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "app.description": "預先建置的 CGI 已封裝在這個 DSM 應用程式中。",
3
+ "app.title": "Binary App"
4
+ }
@@ -2,16 +2,31 @@
2
2
  "use strict";
3
3
  window.DsmPackageApps = window.DsmPackageApps || {};
4
4
  window.DsmPackageApps["__PACKAGE__"] = {
5
- mount(root) {
5
+ // ctx.t(key) returns text from i18n/messages/<locale>.json in DSM's language.
6
+ // ctx.portal exists only with `portal:` in dsm-package.yaml; its panel
7
+ // shows the DSM Application Portal link, or the alias form to administrators.
8
+ mount(root, ctx) {
6
9
  const app = document.createElement("main");
7
10
  app.className = "binary-app";
8
11
  const title = document.createElement("h1");
9
- title.textContent = "Binary App";
12
+ title.textContent = ctx.t("app.title");
10
13
  const description = document.createElement("p");
11
- description.textContent = "Your prebuilt CGI is packaged in this DSM application.";
14
+ description.textContent = ctx.t("app.description");
12
15
  app.append(title, description);
16
+ let portalPanel = null;
17
+ if (ctx.portal) {
18
+ const portal = document.createElement("section");
19
+ portal.setAttribute("class", "dsmapp-portal");
20
+ app.append(portal);
21
+ portalPanel = ctx.portal.mountPanel(portal);
22
+ }
13
23
  root.append(app);
14
- return { unmount() { app.remove(); } };
24
+ return {
25
+ unmount() {
26
+ if (portalPanel) portalPanel.dispose();
27
+ app.remove();
28
+ },
29
+ };
15
30
  },
16
31
  };
17
32
  })();
@@ -22,9 +22,9 @@ const canAuthorize=computed(()=>required.value&&!untrusted.value)
22
22
  const unavailableKey=computed(()=>shown.value?.reason==='credential_not_ready'?'credential':'unavailable')
23
23
  const title=computed(()=>!shown.value?text.value('checking'):required.value?text.value('title'):shown.value.state==='ready'?text.value('ready'):shown.value.state==='not_admin'?text.value('not_admin.title'):shown.value.state==='session_expired'?text.value('session_expired.title'):text.value(unavailableKey.value+'.title'))
24
24
  const body=computed(()=>required.value?text.value('purpose'):shown.value?.state==='not_admin'?text.value('not_admin.body'):shown.value?.state==='session_expired'?text.value('session_expired.body'):shown.value?.state==='unavailable'?text.value(unavailableKey.value+'.body'):text.value('purpose'))
25
- const reasons=['grant_missing','helper_untrusted','cli_untrusted','grant_incompatible','package_identity_changed','maintenance_task_missing','maintenance_task_mismatch','helper_health_rejected','capability_missing']
25
+ const reasons=['grant_missing','upgrade_reapproval','helper_untrusted','cli_untrusted','grant_incompatible','package_identity_changed','maintenance_task_missing','maintenance_task_mismatch','helper_health_rejected','capability_missing']
26
26
  const reason=computed(()=>reasons.includes(shown.value?.reason||'')?shown.value!.reason!:'unknown')
27
- const next=computed(()=>untrusted.value?'next.reinstall':reason.value==='grant_missing'?'next.install':reason.value==='grant_incompatible'?'next.update':'next.repair')
27
+ const next=computed(()=>untrusted.value?'next.reinstall':reason.value==='grant_missing'?'next.install':reason.value==='upgrade_reapproval'||reason.value==='grant_incompatible'?'next.update':'next.repair')
28
28
  async function action(method:'status'|'request'|'revoke'){
29
29
  if(busy.value||disposed)return
30
30
  const current=++sequence
@@ -37,6 +37,8 @@ async function action(method:'status'|'request'|'revoke'){
37
37
  const result=await props.ctx.authorization?.[method]()
38
38
  if(disposed||current!==sequence)return
39
39
  if(!result||result.command!=='authorization'||result.schema_version!==1||!['ready','authorization_required','not_admin','session_expired','unavailable'].includes(result.state))throw Error('invalid status')
40
+ // A reason-only upgrade tombstone never accompanies a usable authorization.
41
+ if(result.reason==='upgrade_reapproval'&&result.state!=='authorization_required')throw Error('invalid status')
40
42
  state.value=result
41
43
  if(result.state==='ready')emit('ready')
42
44
  } catch(e){if(!disposed&&current===sequence){
@@ -54,7 +56,7 @@ onUnmounted(()=>{disposed=true;sequence++;if(typeof unsubscribe==='function')uns
54
56
  <span class="authorization-icon" aria-hidden="true"><component :is="shown?.state==='unavailable'||shown?.state==='session_expired'?TriangleAlert:ShieldCheck" class="authorization-icon-glyph" /></span>
55
57
  <h1 class="authorization-title">{{ title }}</h1>
56
58
  <p class="authorization-body">{{ body }}</p>
57
- <Callout v-if="required" class="authorization-status" :tone="reason==='grant_missing'?'info':'warning'" :title="text('reason.'+reason)">{{ text(next) }}</Callout>
59
+ <Callout v-if="required" class="authorization-status" :tone="['grant_missing','upgrade_reapproval'].includes(reason)?'info':'warning'" :title="text('reason.'+reason)">{{ text(next) }}</Callout>
58
60
  <Callout v-if="failure" class="authorization-failed" tone="warning">{{ text('failed') }}</Callout>
59
61
  <div class="authorization-actions">
60
62
  <Button v-if="canAuthorize" variant="primary" :loading="busy" :disabled="busy" @click="action('request')">{{ busy ? text('authorizing') : text('authorize') }}</Button>
@@ -2,6 +2,7 @@
2
2
  "authorization.title": "{package} benötigt Ihre Erlaubnis",
3
3
  "authorization.purpose": "Dieses Paket benötigt eine Genehmigung für feste Root-Operationen. DSM verarbeitet Ihr Passwort; das Paket sieht oder speichert es nicht.",
4
4
  "authorization.reason.grant_missing": "Root-Operationen sind noch nicht autorisiert.",
5
+ "authorization.reason.upgrade_reapproval": "Das Paket wurde im Paket-Zentrum aktualisiert, daher wurde die bisherige Autorisierung widerrufen.",
5
6
  "authorization.reason.helper_untrusted": "Dem privilegierten Helfer konnte nicht vertraut werden.",
6
7
  "authorization.reason.cli_untrusted": "Dem privilegierten Befehl konnte nicht vertraut werden.",
7
8
  "authorization.reason.grant_incompatible": "Der privilegierte Helfer muss aktualisiert werden.",