@synopackageland/cli 0.6.0 → 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 (585) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/README.md +81 -78
  3. package/dist/add.js +39 -16
  4. package/dist/authorization-sdk.d.ts +7 -0
  5. package/dist/authorization-sdk.js +7 -0
  6. package/dist/build-spk/delegate-scope.d.ts +6 -0
  7. package/dist/build-spk/delegate-scope.js +34 -0
  8. package/dist/build-spk/dsm-arch.d.ts +7 -1
  9. package/dist/build-spk/dsm-arch.js +24 -28
  10. package/dist/build-spk/dsm-platforms.json +39 -0
  11. package/dist/build-spk/dsm-version.d.ts +11 -0
  12. package/dist/build-spk/dsm-version.js +17 -0
  13. package/dist/build-spk/gateway.d.ts +1 -1
  14. package/dist/build-spk/gateway.js +2 -2
  15. package/dist/build-spk/generator.js +30 -5
  16. package/dist/build-spk/notices.js +4 -1
  17. package/dist/build-spk/resource-worker.js +3 -0
  18. package/dist/build-spk/wizard-messages.d.ts +66 -0
  19. package/dist/build-spk/wizard-messages.js +75 -9
  20. package/dist/cli.js +30 -18
  21. package/dist/contract/schema-validate.js +8 -0
  22. package/dist/contract/validate.js +30 -0
  23. package/dist/deploy/deploy.d.ts +1 -1
  24. package/dist/deploy/deploy.js +8 -0
  25. package/dist/deploy/plan.js +1 -1
  26. package/dist/deploy/spk-version.js +1 -1
  27. package/dist/error-registry.js +46 -13
  28. package/dist/host/dsm-client.d.ts +8 -4
  29. package/dist/host/dsm-client.js +10 -4
  30. package/dist/host/types.d.ts +2 -1
  31. package/dist/info.js +18 -2
  32. package/dist/init.d.ts +3 -0
  33. package/dist/init.js +49 -18
  34. package/dist/package-metadata.d.ts +21 -0
  35. package/dist/package-metadata.js +101 -0
  36. package/dist/portal/alias.d.ts +25 -0
  37. package/dist/portal/alias.js +48 -0
  38. package/dist/portal/package.d.ts +33 -0
  39. package/dist/portal/package.js +116 -0
  40. package/dist/runtime/delegate-contract.d.ts +79 -0
  41. package/dist/runtime/delegate-contract.js +27 -0
  42. package/dist/runtime/executor-build.d.ts +1 -1
  43. package/dist/runtime/executor-build.js +5 -4
  44. package/dist/runtime/executor-inspect.d.ts +3 -0
  45. package/dist/runtime/executor-inspect.js +11 -5
  46. package/dist/runtime/executor-policy.d.ts +12 -0
  47. package/dist/runtime/executor-policy.js +43 -3
  48. package/dist/runtime/executor-scope-task.d.ts +4 -0
  49. package/dist/runtime/executor-scope-task.js +21 -0
  50. package/dist/runtime/executor-tasks.d.ts +2 -1
  51. package/dist/runtime/executor-tasks.js +4 -4
  52. package/dist/skill.js +1 -0
  53. package/dist/standalone/api-permissions.d.ts +10 -0
  54. package/dist/standalone/api-permissions.js +19 -2
  55. package/dist/standalone/background.d.ts +16 -0
  56. package/dist/standalone/background.js +69 -2
  57. package/dist/standalone/binary.d.ts +1 -1
  58. package/dist/standalone/binary.js +22 -8
  59. package/dist/standalone/build.d.ts +1 -1
  60. package/dist/standalone/build.js +38 -8
  61. package/dist/standalone/daemon.d.ts +1 -1
  62. package/dist/standalone/daemon.js +2 -1
  63. package/dist/standalone/dsm-window.d.ts +3 -1
  64. package/dist/standalone/dsm-window.js +72 -15
  65. package/dist/standalone/inspect-spk.d.ts +9 -1
  66. package/dist/standalone/inspect-spk.js +119 -26
  67. package/dist/standalone/lifecycle.js +86 -8
  68. package/dist/standalone/manifest.d.ts +13 -0
  69. package/dist/standalone/manifest.js +57 -8
  70. package/dist/standalone/native-authorization-window.d.ts +2 -0
  71. package/dist/standalone/native-authorization-window.js +30 -0
  72. package/dist/standalone/native-layout.d.ts +82 -0
  73. package/dist/standalone/native-layout.js +377 -0
  74. package/dist/standalone/native-root-window.d.ts +26 -0
  75. package/dist/standalone/native-root-window.js +282 -0
  76. package/dist/standalone/portal-window.d.ts +21 -0
  77. package/dist/standalone/portal-window.js +155 -0
  78. package/dist/standalone/portal.d.ts +4 -0
  79. package/dist/standalone/portal.js +4 -0
  80. package/dist/standalone/resource.d.ts +5 -3
  81. package/dist/standalone/resource.js +6 -3
  82. package/dist/standalone/root-window.js +62 -15
  83. package/dist/standalone/spk.d.ts +1 -1
  84. package/dist/standalone/spk.js +25 -7
  85. package/dist/templates/_daemon/main_test.go +61 -8
  86. package/dist/templates/_executor/README.md +127 -16
  87. package/dist/templates/_executor/anchor.go +146 -0
  88. package/dist/templates/_executor/anchor_test.go +126 -0
  89. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  90. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  91. package/dist/templates/_executor/copy.go +64 -0
  92. package/dist/templates/_executor/copy_test.go +88 -0
  93. package/dist/templates/_executor/dsm.go +23 -10
  94. package/dist/templates/_executor/dsm_test.go +95 -1
  95. package/dist/templates/_executor/executor.go +260 -49
  96. package/dist/templates/_executor/executor_test.go +36 -6
  97. package/dist/templates/_executor/identity.go +27 -1
  98. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  99. package/dist/templates/_executor/identity_linux.go +82 -0
  100. package/dist/templates/_executor/identity_security_test.go +43 -0
  101. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  102. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  103. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  104. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  105. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  106. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  107. package/dist/templates/_executor/lifecycle_test.go +11 -0
  108. package/dist/templates/_executor/link_linux.go +28 -0
  109. package/dist/templates/_executor/long_running_test.go +530 -0
  110. package/dist/templates/_executor/main.go +72 -20
  111. package/dist/templates/_executor/move_linux.go +490 -0
  112. package/dist/templates/_executor/move_linux_test.go +460 -0
  113. package/dist/templates/_executor/move_validation.go +46 -0
  114. package/dist/templates/_executor/move_validation_test.go +187 -0
  115. package/dist/templates/_executor/operations.go +926 -0
  116. package/dist/templates/_executor/platform_linux.go +24 -173
  117. package/dist/templates/_executor/platform_linux_test.go +9 -65
  118. package/dist/templates/_executor/platform_other.go +30 -4
  119. package/dist/templates/_executor/process.go +3 -0
  120. package/dist/templates/_executor/records.go +242 -0
  121. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  122. package/dist/templates/_executor/scope_client.go +37 -0
  123. package/dist/templates/_executor/scope_contract.go +12 -0
  124. package/dist/templates/_executor/selected_scope.go +146 -0
  125. package/dist/templates/_executor/selected_scope_test.go +186 -0
  126. package/dist/templates/_executor/status_disabled_test.go +33 -0
  127. package/dist/templates/_executor/store.go +4 -3
  128. package/dist/templates/_executor/supervise.go +109 -0
  129. package/dist/templates/_executor/supervise_test.go +130 -0
  130. package/dist/templates/_executor/task_race_review_test.go +1 -1
  131. package/dist/templates/_executor/tasks.go +7 -8
  132. package/dist/templates/_executor/tasks_review_test.go +1 -1
  133. package/dist/templates/_executor/tasks_test.go +4 -4
  134. package/dist/templates/_executor/watch.go +8 -0
  135. package/dist/templates/_executor/watch_test.go +23 -2
  136. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  137. package/dist/templates/_shared/AGENTS.md +129 -0
  138. package/dist/templates/binary/dsm-package.yaml +24 -0
  139. package/dist/templates/binary/i18n/messages/de.json +4 -0
  140. package/dist/templates/binary/i18n/messages/en.json +4 -0
  141. package/dist/templates/binary/i18n/messages/es.json +4 -0
  142. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  143. package/dist/templates/binary/i18n/messages/it.json +4 -0
  144. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  145. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  146. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  147. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  148. package/dist/templates/binary/ui/app.js +19 -4
  149. package/dist/templates/native-authorization/AuthorizationGate.vue +91 -0
  150. package/dist/templates/native-authorization/i18n/de.json +37 -0
  151. package/dist/templates/native-authorization/i18n/en.json +37 -0
  152. package/dist/templates/native-authorization/i18n/es.json +37 -0
  153. package/dist/templates/native-authorization/i18n/fr.json +37 -0
  154. package/dist/templates/native-authorization/i18n/index.ts +15 -0
  155. package/dist/templates/native-authorization/i18n/it.json +37 -0
  156. package/dist/templates/native-authorization/i18n/ja.json +37 -0
  157. package/dist/templates/native-authorization/i18n/ko.json +37 -0
  158. package/dist/templates/native-authorization/i18n/zh-CN.json +37 -0
  159. package/dist/templates/native-authorization/i18n/zh-TW.json +37 -0
  160. package/dist/templates/native-go/README.md +43 -10
  161. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  162. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  163. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  164. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  165. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  166. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  167. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  168. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  169. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  170. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  171. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  172. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  173. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  174. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  175. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  176. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  177. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  178. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  179. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  180. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  181. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  182. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  183. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  184. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  185. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  186. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  187. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  188. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  189. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  190. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  191. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  192. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  193. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  194. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  195. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  196. package/dist/templates/native-go/dsm-package.yaml +20 -1
  197. package/dist/templates/native-go/i18n/glossary.json +2 -1
  198. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  199. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  200. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  201. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  202. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  203. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  204. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  205. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  206. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  207. package/dist/templates/native-go/ui/app.css +10 -0
  208. package/dist/templates/native-go/ui/app.js +11 -1
  209. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  210. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  211. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  212. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  213. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  214. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  215. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  216. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  217. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  218. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  219. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  220. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  221. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  222. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  223. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  224. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  225. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  226. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  227. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  228. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  229. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  230. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  231. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  232. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  233. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  234. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  235. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  236. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  237. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  238. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  239. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  240. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  241. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  242. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  243. package/dist/templates/native-go-delegate/test.sh +1 -0
  244. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  245. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  246. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  247. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  248. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  249. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  250. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  251. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  252. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  253. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  254. package/dist/templates/native-go-vue/README.md +4 -0
  255. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  256. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  257. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  258. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  259. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  260. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  261. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  262. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  263. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  264. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  265. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  266. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  267. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  268. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  269. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  270. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  271. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  272. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  273. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  274. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  275. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  276. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  277. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  278. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  279. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  280. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  281. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  282. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  283. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  284. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  285. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  286. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  287. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  288. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  289. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  290. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  291. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  292. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  293. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  294. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  295. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  296. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  297. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  298. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  299. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  300. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  301. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  302. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  303. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  304. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  305. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  306. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  307. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  308. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  309. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  310. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  311. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  312. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  313. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  314. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  319. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  320. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  321. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  322. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  323. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  324. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  325. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  326. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  327. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  328. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  329. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  330. package/dist/validate.js +2 -1
  331. package/package.json +12 -2
  332. package/schema/package-metadata.schema.json +101 -0
  333. package/schema/synology-app.schema.json +112 -0
  334. package/skills/SKILL.md +109 -3
  335. package/skills/reference/create.md +101 -2
  336. package/skills/reference/deploy.md +2 -2
  337. package/skills/reference/files.md +113 -2
  338. package/skills/reference/identity.md +70 -1
  339. package/skills/reference/portal.md +17 -0
  340. package/skills/reference/ui-vue.md +12 -0
  341. package/templates/_daemon/main_test.go +61 -8
  342. package/templates/_executor/README.md +127 -16
  343. package/templates/_executor/anchor.go +146 -0
  344. package/templates/_executor/anchor_test.go +126 -0
  345. package/templates/_executor/apppriv_review_test.go +1 -1
  346. package/templates/_executor/cancel_contract_test.go +236 -0
  347. package/templates/_executor/copy.go +64 -0
  348. package/templates/_executor/copy_test.go +88 -0
  349. package/templates/_executor/dsm.go +23 -10
  350. package/templates/_executor/dsm_test.go +95 -1
  351. package/templates/_executor/executor.go +260 -49
  352. package/templates/_executor/executor_test.go +36 -6
  353. package/templates/_executor/identity.go +27 -1
  354. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  355. package/templates/_executor/identity_linux.go +82 -0
  356. package/templates/_executor/identity_security_test.go +43 -0
  357. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  358. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  359. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  360. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  361. package/templates/_executor/internal/taskscript/control.sh +15 -10
  362. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  363. package/templates/_executor/lifecycle_test.go +11 -0
  364. package/templates/_executor/link_linux.go +28 -0
  365. package/templates/_executor/long_running_test.go +530 -0
  366. package/templates/_executor/main.go +72 -20
  367. package/templates/_executor/move_linux.go +490 -0
  368. package/templates/_executor/move_linux_test.go +460 -0
  369. package/templates/_executor/move_validation.go +46 -0
  370. package/templates/_executor/move_validation_test.go +187 -0
  371. package/templates/_executor/operations.go +926 -0
  372. package/templates/_executor/platform_linux.go +24 -173
  373. package/templates/_executor/platform_linux_test.go +9 -65
  374. package/templates/_executor/platform_other.go +30 -4
  375. package/templates/_executor/process.go +3 -0
  376. package/templates/_executor/records.go +242 -0
  377. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  378. package/templates/_executor/scope_client.go +37 -0
  379. package/templates/_executor/scope_contract.go +12 -0
  380. package/templates/_executor/selected_scope.go +146 -0
  381. package/templates/_executor/selected_scope_test.go +186 -0
  382. package/templates/_executor/status_disabled_test.go +33 -0
  383. package/templates/_executor/store.go +4 -3
  384. package/templates/_executor/supervise.go +109 -0
  385. package/templates/_executor/supervise_test.go +130 -0
  386. package/templates/_executor/task_race_review_test.go +1 -1
  387. package/templates/_executor/tasks.go +7 -8
  388. package/templates/_executor/tasks_review_test.go +1 -1
  389. package/templates/_executor/tasks_test.go +4 -4
  390. package/templates/_executor/watch.go +8 -0
  391. package/templates/_executor/watch_test.go +23 -2
  392. package/templates/_executor/write_review_linux_test.go +4 -12
  393. package/templates/_shared/AGENTS.md +129 -0
  394. package/templates/binary/dsm-package.yaml +24 -0
  395. package/templates/binary/i18n/messages/de.json +4 -0
  396. package/templates/binary/i18n/messages/en.json +4 -0
  397. package/templates/binary/i18n/messages/es.json +4 -0
  398. package/templates/binary/i18n/messages/fr.json +4 -0
  399. package/templates/binary/i18n/messages/it.json +4 -0
  400. package/templates/binary/i18n/messages/ja.json +4 -0
  401. package/templates/binary/i18n/messages/ko.json +4 -0
  402. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  403. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  404. package/templates/binary/ui/app.js +19 -4
  405. package/templates/native-authorization/AuthorizationGate.vue +91 -0
  406. package/templates/native-authorization/i18n/de.json +37 -0
  407. package/templates/native-authorization/i18n/en.json +37 -0
  408. package/templates/native-authorization/i18n/es.json +37 -0
  409. package/templates/native-authorization/i18n/fr.json +37 -0
  410. package/templates/native-authorization/i18n/index.ts +15 -0
  411. package/templates/native-authorization/i18n/it.json +37 -0
  412. package/templates/native-authorization/i18n/ja.json +37 -0
  413. package/templates/native-authorization/i18n/ko.json +37 -0
  414. package/templates/native-authorization/i18n/zh-CN.json +37 -0
  415. package/templates/native-authorization/i18n/zh-TW.json +37 -0
  416. package/templates/native-go/README.md +43 -10
  417. package/templates/native-go/backend/cmd/app/main.go +27 -7
  418. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  419. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  420. package/templates/native-go/backend/internal/api/registry.go +11 -0
  421. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  422. package/templates/native-go/backend/internal/api/server.go +18 -5
  423. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  424. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  425. package/templates/native-go/backend/internal/app/portal.go +30 -0
  426. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  427. package/templates/native-go/backend/internal/app/routes.go +8 -2
  428. package/templates/native-go/backend/internal/background/runner.go +1 -1
  429. package/templates/native-go/backend/internal/background/store.go +16 -14
  430. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  431. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  432. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  433. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  434. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  435. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  436. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  437. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  438. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  439. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  440. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  441. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  442. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  443. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  444. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  445. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  446. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  447. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  448. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  449. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  450. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  451. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  452. package/templates/native-go/dsm-package.yaml +20 -1
  453. package/templates/native-go/i18n/glossary.json +2 -1
  454. package/templates/native-go/i18n/messages/de.json +1 -0
  455. package/templates/native-go/i18n/messages/en.json +1 -0
  456. package/templates/native-go/i18n/messages/es.json +1 -0
  457. package/templates/native-go/i18n/messages/fr.json +1 -0
  458. package/templates/native-go/i18n/messages/it.json +1 -0
  459. package/templates/native-go/i18n/messages/ja.json +1 -0
  460. package/templates/native-go/i18n/messages/ko.json +1 -0
  461. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  462. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  463. package/templates/native-go/ui/app.css +10 -0
  464. package/templates/native-go/ui/app.js +11 -1
  465. package/templates/native-go-delegate/DELEGATE.md +126 -5
  466. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  467. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  468. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  469. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  470. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  471. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  472. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  473. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  474. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  475. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  476. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  477. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  478. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  479. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  480. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  481. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  482. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  483. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  484. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  485. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  486. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  487. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  488. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  489. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  490. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  491. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  492. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  493. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  494. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  495. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  496. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  497. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  498. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  499. package/templates/native-go-delegate/test.sh +1 -0
  500. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  501. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  502. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  503. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  504. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  505. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  506. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  507. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  508. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  509. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  510. package/templates/native-go-vue/README.md +4 -0
  511. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  512. package/templates/native-go-vue/frontend/package.json +1 -1
  513. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  514. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  515. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  516. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  517. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  518. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  519. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  520. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  521. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  522. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  523. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  524. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  525. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  526. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  527. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  528. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  529. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  530. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  531. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  532. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  533. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  534. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  535. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  536. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  537. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  538. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  539. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  540. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  541. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  542. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  543. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  544. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  545. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  546. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  547. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  548. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  549. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  550. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  551. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  552. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  553. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  554. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  555. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  556. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  557. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  558. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  559. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  560. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  561. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  562. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  563. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  564. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  565. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  566. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  567. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  568. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  569. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  570. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  571. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  572. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  573. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  574. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  575. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  576. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  577. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  578. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  579. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  580. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  581. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  582. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  583. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  584. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  585. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -18,6 +18,10 @@ subscriptions; unmount disposes subscriptions and ignores late responses.
18
18
  operations. `--with jobs` adds package-account jobs and schedules; it combines with root-ops and files. `--with files` enables current-session File Station access,
19
19
  and the background picker uses the retained session provider.
20
20
 
21
+ With `portal:` declared (`synopkgland add portal`), the status page shows
22
+ `src/portal/PortalSettings.vue`: the DSM Application Portal link, the alias form
23
+ for administrators, and the Control Panel shortcut.
24
+
21
25
  Use public library controls, the embedded theme, per-app modal scope and unique
22
26
  Vue ID prefixes. Backend route permissions and DSM checks remain authoritative.
23
27
  Generated build and unit tests do not establish NAS runtime acceptance.
@@ -7,7 +7,7 @@
7
7
  "name": "native-dsm-vue-app",
8
8
  "dependencies": {
9
9
  "@lucide/vue": "1.48.0",
10
- "@synopackageland/ui": "0.8.0",
10
+ "@synopackageland/ui": "0.8.1",
11
11
  "vue": "3.5.43"
12
12
  },
13
13
  "devDependencies": {
@@ -1147,9 +1147,9 @@
1147
1147
  }
1148
1148
  },
1149
1149
  "node_modules/@synopackageland/ui": {
1150
- "version": "0.8.0",
1151
- "resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.8.0.tgz",
1152
- "integrity": "sha512-RR7vvIqZbVtp1jRG16/sqILGQjgAuxSN5eAlXluWshUTh9xcIMpsC39/j9L63iZyic58Drw7KiZyZyqYQL4NHw==",
1150
+ "version": "0.8.1",
1151
+ "resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.8.1.tgz",
1152
+ "integrity": "sha512-3BykWpWrDGjIDVjdL0WeMwD0oO3z1/IIEishR9+2sYAL/spd3G9sEQL1J26jHl0EfvoPvwPZHNJdIL14JQJQYA==",
1153
1153
  "license": "MIT",
1154
1154
  "dependencies": {
1155
1155
  "@internationalized/date": "3.12.4",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-dsm-vue-app", "private": true, "type": "module",
3
3
  "scripts": {"build": "vue-tsc --noEmit && vite build", "test": "vitest run"},
4
- "dependencies": {"@lucide/vue": "1.48.0", "@synopackageland/ui": "0.8.0", "vue": "3.5.43"},
4
+ "dependencies": {"@lucide/vue": "1.48.0", "@synopackageland/ui": "0.8.1", "vue": "3.5.43"},
5
5
  "devDependencies": {"@vitejs/plugin-vue": "6.0.9", "vite": "7.3.6", "typescript": "5.9.3", "vue-tsc": "3.3.11", "vitest": "4.1.11", "jsdom": "27.4.0"}
6
6
  }
@@ -2,15 +2,18 @@
2
2
  import {computed, onMounted, onUnmounted, ref, watch} from 'vue'
3
3
  import {AppRoot, ConfigProvider, Button, Callout, Form, FormActions, FormItem, SettingsSection, Textarea} from '@synopackageland/ui'
4
4
  import {uiLocale} from './nas/locale'
5
+ import {appCopy} from './i18n'
5
6
  import type {AppStatus, Context} from './context'
6
7
  import AppShell from './shell/AppShell.vue'
7
8
  import CapabilityPanels from './CapabilityPanels.vue'
8
9
  import FilesPage from './files/FilesPage.vue'
10
+ import PortalSettings from './portal/PortalSettings.vue'
9
11
  import {shellPages, type ShellPage} from './shell/pages'
10
12
  import {createSession} from './identity/session'
11
13
  import LoginPage from './identity/LoginPage.vue'
12
14
  const props = defineProps<{ctx: Context}>()
13
15
  const login = createSession(props.ctx)
16
+ const copy = computed(() => appCopy(props.ctx.locale))
14
17
  const app = ref<AppStatus>()
15
18
  const busy = ref(false)
16
19
  const error = ref('')
@@ -25,7 +28,8 @@ const pages = computed(() => shellPages({background:backgroundEnabled}))
25
28
  const subtitle = computed(() => app.value ? `${app.value.package} · ${app.value.user} · ${app.value.version}` : undefined)
26
29
  function message(e: unknown) {
27
30
  const code = (e as {code?: string})?.code || 'request_failed'
28
- return ({session_required:'Your DSM session expired. Sign in again.',admin_required:'A DSM administrator must perform this action.',auth_unavailable:'DSM authentication is unavailable. Refresh to try again.'} as Record<string,string>)[code] || `Request failed (${code}). Refresh and try again.`
31
+ const errors = copy.value.errors as Record<string, string>
32
+ return (code !== 'request_failed' && errors[code]) || errors.request_failed!.replace('{code}', code)
29
33
  }
30
34
  async function refresh() {
31
35
  if(disposed || busy.value || (login.enabled.value && !login.subject.value)) return
@@ -39,7 +43,7 @@ async function refresh() {
39
43
  async function save() {
40
44
  if(disposed || busy.value) return
41
45
  busy.value = true; error.value='';notice.value=''
42
- try {await login.call('greeting.set',{text:text.value});if(!disposed) notice.value='Greeting saved.'}
46
+ try {await login.call('greeting.set',{text:text.value});if(!disposed) notice.value=copy.value.saved}
43
47
  catch(e) {if(!disposed) error.value=message(e)} finally {if(!disposed) busy.value=false}
44
48
  }
45
49
  watch(login.subject, (subject, previous) => {
@@ -60,22 +64,23 @@ onUnmounted(()=>{disposed=true;unsubscribe?.();login.dispose();text.value=''})
60
64
  <template>
61
65
  <AppRoot modal-scope="app" style="height: 100%">
62
66
  <ConfigProvider :locale="uiLocale(ctx.locale)">
63
- <p v-if="!login.ready.value" role="status">Checking session…</p>
64
- <LoginPage v-else-if="login.enabled.value && !login.subject.value" :session="login" />
67
+ <p v-if="!login.ready.value" role="status">{{ copy.checking }}</p>
68
+ <LoginPage v-else-if="login.enabled.value && !login.subject.value" :session="login" :locale="ctx.locale" />
65
69
  <AppShell v-else v-model="page" :locale="ctx.locale" :pages="pages" :description="subtitle">
66
- <template #actions><Button variant="secondary" :disabled="busy" @click="refresh">Refresh</Button><Button v-if="login.enabled.value" variant="secondary" :disabled="busy" @click="signOut">Sign out</Button></template>
67
- <div class="integration-app" :aria-busy="busy" aria-label="Package settings">
68
- <p v-if="busy" role="status">Loading package data…</p>
69
- <Callout v-if="error" tone="danger" title="Action required"><span role="alert">{{ error }}</span></Callout>
70
+ <template #actions><Button variant="secondary" :disabled="busy" @click="refresh">{{ copy.refresh }}</Button><Button v-if="login.enabled.value" variant="secondary" :disabled="busy" @click="signOut">{{ copy.signOut }}</Button></template>
71
+ <div class="integration-app" :aria-busy="busy" :aria-label="copy.settings">
72
+ <p v-if="busy" role="status">{{ copy.loading }}</p>
73
+ <Callout v-if="error" tone="danger" :title="copy.actionRequired"><span role="alert">{{ error }}</span></Callout>
70
74
  <p v-if="notice" role="status">{{ notice }}</p>
71
75
  <div data-page="status" v-show="page==='status'">
72
- <SettingsSection title="Package status" :collapsible="false">
76
+ <SettingsSection :title="copy.statusTitle" :collapsible="false">
73
77
  <p v-if="app">{{ app.package }} · {{ app.user }} · {{ app.version }}</p>
74
- <p v-else-if="error" role="status">Status could not be loaded. Refresh to try again.</p>
78
+ <p v-else-if="error" role="status">{{ copy.statusError }}</p>
75
79
  </SettingsSection>
76
- <SettingsSection v-if="app?.admin" title="Greeting" :collapsible="false">
77
- <Form @submit.prevent="save"><FormItem label="Greeting"><Textarea v-model="text" :disabled="busy" /></FormItem>
78
- <FormActions><Button variant="primary" :disabled="busy || !text.trim()" @click="save">Save</Button></FormActions>
80
+ <PortalSettings v-if="ctx.portal && app" :key="app.user" :portal="ctx.portal" />
81
+ <SettingsSection v-if="app?.admin" :title="copy.greeting" :collapsible="false">
82
+ <Form @submit.prevent="save"><FormItem :label="copy.greeting"><Textarea v-model="text" :disabled="busy" /></FormItem>
83
+ <FormActions><Button variant="primary" :disabled="busy || !text.trim()" @click="save">{{ copy.save }}</Button></FormActions>
79
84
  </Form>
80
85
  </SettingsSection>
81
86
  </div>
@@ -35,10 +35,41 @@ export interface HostAdapter {
35
35
  subscribe(event: HostEvent, listener: () => void): HostSubscriptionResult
36
36
  dispose(): void
37
37
  }
38
+ export interface PortalStatus {
39
+ state: 'active' | 'inactive' | 'unavailable'
40
+ // Set when active: https://<the NAS address in use>/<alias>/
41
+ alias?: string
42
+ url?: string
43
+ // An administrator can set or clear the alias here; otherwise Control Panel or nothing.
44
+ canManage: boolean
45
+ controlPanel: boolean
46
+ defaultAlias: string
47
+ }
48
+
49
+ // Rejects {code}: portal_alias_invalid, portal_alias_reserved, portal_alias_in_use, admin_required, portal_unavailable.
50
+ export interface PortalAccess {
51
+ // SDK panel text in DSM's language, keyed like the error codes plus title, link, apply, …
52
+ messages: Record<string, string>
53
+ status(): Promise<PortalStatus>
54
+ apply(alias: string): Promise<PortalStatus>
55
+ disable(): Promise<PortalStatus>
56
+ openControlPanel(): void
57
+ // The SDK's plain panel; Vue apps use portal/PortalSettings.vue instead.
58
+ mountPanel(element: HTMLElement): {refresh(): Promise<void>; dispose(): void}
59
+ }
60
+
38
61
  export interface Context {
62
+ // Explicit long-running opt-in; bind with createBackgroundSaveFacade and an
63
+ // owner-bound App job metadata provider. Legacy downloads use the other panel.
64
+ delegate?: import('./delegate/long-running').BackgroundSaveFacade
39
65
  files?: {endpoint:string;token?:()=>string|undefined}
40
66
  displayName?: string
41
67
  executor?: import('./delegate/executor-setup').ExecutorSetup
68
+ // DSM's signed-in account. isAdmin only hides or disables administrator
69
+ // settings; the backend enforces access with permissions.json and RequireAdmin.
70
+ user?: {readonly name: string; readonly isAdmin: boolean}
71
+ // DSM Application Portal, present only when dsm-package.yaml declares portal.
72
+ portal?: PortalAccess
42
73
  call<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>
43
74
  reopen(fn: () => void): () => void
44
75
  locale: string
@@ -1,24 +1,68 @@
1
1
  <script setup lang="ts">
2
2
  import {computed,onBeforeUnmount,onMounted,ref} from 'vue'
3
- import {Button,Callout,Form,FormActions,FormItem,Input,SettingsSection} from '@synopackageland/ui'
3
+ import {Button,Callout,Form,FormActions,FormItem,Input,Select,SettingsSection} from '@synopackageland/ui'
4
+ import type {Context} from '../context'
5
+ import type {ScopeSelection} from './long-running'
4
6
  import {useExecutorSetup,type CapabilityStatus} from './executor-setup'
5
7
  import {delegateCopy} from './copy'
6
- const props=defineProps<{admin:boolean;locale?:string}>()
8
+ const props=defineProps<{admin:boolean;locale?:string;ctx?:Pick<Context,'call'>}>()
9
+ const emit=defineEmits<{confirmed:[]}>()
7
10
  const setup=useExecutorSetup(),copy=computed(()=>delegateCopy(props.locale))
8
11
  const status=ref<CapabilityStatus>(),password=ref(''),busy=ref(false),error=ref(false)
12
+ const scope=ref<{scope_mode?:string;preselection?:string;selection?:ScopeSelection|null;mismatch?:boolean}>(),chosen=ref(''),shares=ref<Array<{label:string;value:string}>>([])
13
+ const dynamic=computed(()=>scope.value?.scope_mode==='install-selected')
14
+ const options=computed(()=>[{label:copy.value.homeScope,value:'home'},...shares.value])
15
+ const canConfirm=computed(()=>!dynamic.value||!!chosen.value&&options.value.some(o=>o.value===chosen.value))
16
+ const needsScope=computed(()=>dynamic.value&&!scope.value?.selection)
17
+ const changed=computed(()=>chosen.value!==scope.value?.selection?.scope)
9
18
  let disposed=false
10
- async function refresh(){if(!props.admin||busy.value)return;busy.value=true;error.value=false;try{const next=await setup.status();if(!disposed)status.value=next}catch{if(!disposed)error.value=true}finally{if(!disposed)busy.value=false}}
11
- async function authorize(){if(!props.admin||busy.value||!password.value)return;busy.value=true;error.value=false;const entered=password.value;password.value='';try{const next=await setup.authorize(entered);if(!disposed)status.value=next}catch{if(!disposed)error.value=true}finally{if(!disposed)busy.value=false}}
19
+ async function load(approved?:CapabilityStatus){
20
+ const next=approved??await setup.status()
21
+ if(disposed)return
22
+ status.value=next
23
+ if(!props.ctx)return
24
+ const detail=await props.ctx.call<NonNullable<typeof scope.value>>('delegate.status')
25
+ if(disposed)return
26
+ scope.value=detail
27
+ if(detail.scope_mode!=='install-selected')return
28
+ const all:Array<{label:string;value:string}>=[]
29
+ let offset=0
30
+ for(let count=0;count<128;count++){
31
+ const page=await props.ctx.call<{shares:Array<{name:string;path:string;isdir:boolean}>;total:number;offset:number}>('bg.browse',{offset})
32
+ if(disposed)return
33
+ if(!Array.isArray(page.shares)||!Number.isSafeInteger(page.total)||page.total<0||!page.shares.length&&offset<page.total)throw {code:'EXECUTOR_PROTOCOL'}
34
+ for(const entry of page.shares){if(entry.isdir&&entry.path==='/'+entry.name&&!['home','homes'].includes(entry.name.toLowerCase()))all.push({label:entry.name,value:'shared:'+entry.name})}
35
+ offset+=page.shares.length
36
+ if(offset>=page.total)break
37
+ if(count===127)throw {code:'EXECUTOR_PROTOCOL'}
38
+ }
39
+ shares.value=all
40
+ chosen.value=detail.selection?.scope??detail.preselection??''
41
+ }
42
+ async function refresh(){if(!props.admin||busy.value)return;busy.value=true;error.value=false;try{await load()}catch{if(!disposed)error.value=true}finally{if(!disposed)busy.value=false}}
43
+ async function authorize(){
44
+ if(!props.admin||busy.value||!password.value||!canConfirm.value||error.value)return
45
+ busy.value=true;error.value=false;const entered=password.value;password.value=''
46
+ try{const next=dynamic.value?await setup.authorize(entered,chosen.value):await setup.authorize(entered);await load(next);if(!disposed)emit('confirmed')}
47
+ catch{if(!disposed)error.value=true}
48
+ finally{if(!disposed)busy.value=false}
49
+ }
12
50
  onMounted(()=>void refresh());onBeforeUnmount(()=>{disposed=true;password.value=''})
13
51
  </script>
14
52
  <template>
15
53
  <SettingsSection v-if="admin" :title="copy.setupTitle" :collapsible="false" :aria-busy="busy">
16
54
  <p>{{ copy.setupHelp }}</p>
17
55
  <Callout v-if="error" tone="danger" role="alert">{{ copy.setupError }}</Callout>
18
- <p role="status">{{ status?.state==='ready'?copy.ready:status?.state==='needs-admin-approval'?copy.needs:copy.blocked }}</p>
19
- <Form v-if="status?.state==='needs-admin-approval'" @submit.prevent="authorize">
20
- <FormItem :label="copy.password" label-for="delegate-password"><Input id="delegate-password" v-model="password" type="password" autocomplete="current-password" :disabled="busy" /></FormItem>
21
- <FormActions><Button type="submit" variant="primary" :disabled="busy||!password">{{ copy.authorize }}</Button></FormActions>
56
+ <p role="status">{{ status?.state==='ready'?(needsScope?copy.scopeNeeds:copy.ready):status?.state==='needs-admin-approval'?copy.needs:copy.blocked }}</p>
57
+ <p v-if="scope?.selection">{{ copy.protectedScope }}: {{ scope.selection.root }}</p>
58
+ <Callout v-if="scope?.mismatch" tone="warning">{{ copy.scopeMismatch }} {{ copy.preselection }}: {{ scope.preselection }} · {{ copy.protectedScope }}: {{ scope.selection?.root }}</Callout>
59
+ <Form v-if="status?.state==='needs-admin-approval'||dynamic&&status?.state==='ready'" @submit.prevent="authorize">
60
+ <template v-if="dynamic">
61
+ <p>{{ scope?.selection?copy.scopeChangeHelp:copy.preselectionHelp }}</p>
62
+ <FormItem :label="copy.scopeLabel" label-for="delegate-scope"><Select id="delegate-scope" v-model="chosen" :options="options" :disabled="busy||error" /></FormItem>
63
+ </template>
64
+ <FormItem :label="copy.password" label-for="delegate-password"><Input id="delegate-password" v-model="password" type="password" autocomplete="current-password" :disabled="busy||error" /></FormItem>
65
+ <FormActions><Button type="submit" variant="primary" :disabled="busy||error||!password||!canConfirm||dynamic&&!!scope?.selection&&!changed">{{ dynamic?(scope?.selection?copy.confirmScope:copy.confirmShare):copy.authorize }}</Button></FormActions>
22
66
  </Form>
23
67
  <Button variant="secondary" :disabled="busy" @click="refresh">{{ copy.refresh }}</Button>
24
68
  </SettingsSection>
@@ -0,0 +1,11 @@
1
+ import en from './i18n/en.json'
2
+ import zhTW from './i18n/zh-TW.json'
3
+ import zhCN from './i18n/zh-CN.json'
4
+ import ja from './i18n/ja.json'
5
+ import ko from './i18n/ko.json'
6
+ import de from './i18n/de.json'
7
+ import fr from './i18n/fr.json'
8
+ import es from './i18n/es.json'
9
+ import it from './i18n/it.json'
10
+ export type DelegateCopy=typeof en
11
+ export const delegateCatalogs:Record<string,DelegateCopy>={en,'zh-TW':zhTW,'zh-CN':zhCN,ja,ko,de,fr,es,it}
@@ -0,0 +1,12 @@
1
+ import {expect,test} from 'vitest'
2
+ import {delegateCopy} from './copy'
3
+ import {saveCopy} from './save-copy'
4
+ import zhCN from './i18n/zh-CN.json'
5
+ import saveJa from './i18n/save/ja.json'
6
+ test('delegate and save copy follow the DSM language across all nine catalogs',()=>{
7
+ expect(delegateCopy('chs')).toMatchObject(zhCN)
8
+ expect(delegateCopy('zh_CN')).toMatchObject(zhCN)
9
+ expect(saveCopy('jpn')).toEqual(saveJa)
10
+ expect(delegateCopy('unknown').refresh).toBe('Refresh')
11
+ expect(saveCopy().refresh).toBe('Refresh')
12
+ })
@@ -1,4 +1,4 @@
1
- import en from './i18n/en.json'
2
- import zh from './i18n/zh-TW.json'
3
- export function delegateCopy(locale='en'){return locale.toLowerCase().startsWith('zh')?zh:en}
4
- export function errorCopy(error:unknown,copy:typeof en){const code=(error as {code?:string})?.code;return ['session_required','AUTH_DENIED'].includes(code??'')?copy.session:['DENIED','SCOPE_DENIED','PERMISSION_DENIED','APP_DENIED','INELIGIBLE','denied'].includes(code??'')?copy.denied:copy.error}
1
+ import {uiLocale} from '../nas/locale'
2
+ import {delegateCatalogs,type DelegateCopy} from './catalogs'
3
+ export function delegateCopy(locale='en'){return delegateCatalogs[uiLocale(locale)]!}
4
+ export function errorCopy(error:unknown,copy:DelegateCopy){const code=(error as {code?:string})?.code;return ['session_required','AUTH_DENIED'].includes(code??'')?copy.session:['DENIED','SCOPE_DENIED','PERMISSION_DENIED','APP_DENIED','INELIGIBLE','denied'].includes(code??'')?copy.denied:copy.error}
@@ -1,11 +1,13 @@
1
1
  import {inject,type InjectionKey} from 'vue'
2
2
  export type CapabilityState='unsupported'|'needs-admin-approval'|'ready'|'blocked'|'incompatible'
3
- export interface CapabilityStatus {state:CapabilityState;reason?:string}
3
+ // requested: a scope change whose root task may still land; it applied once
4
+ // status() reports that scope with a generation above this one.
5
+ export interface CapabilityStatus {state:CapabilityState;reason?:string;requested?:{scope:string;generation:number}}
4
6
  // p2-setup supplies the implementation through app.provide(ExecutorSetupKey, setup).
5
7
  // Passwords belong only to this call and must never be persisted or logged.
6
8
  export interface ExecutorSetup {
7
9
  status():Promise<CapabilityStatus>
8
- authorize(password:string):Promise<CapabilityStatus>
10
+ authorize(password:string,selectedScope?:string):Promise<CapabilityStatus>
9
11
  control(action:'start'|'stop'|'purge'):Promise<CapabilityStatus>
10
12
  }
11
13
  export const ExecutorSetupKey:InjectionKey<ExecutorSetup>=Symbol('executor-setup')
@@ -0,0 +1,100 @@
1
+ import {expect,test,vi} from 'vitest'
2
+ import {createBackgroundSaveFacade} from './facade'
3
+ test('destination lookup returns protected selection and rejects a grant outside its root',async()=>{
4
+ const selection={scope:'shared:reports',root:'/reports',generation:2}
5
+ const destination={id:'mine',key:'save',path:'/reports/exports',username:'alice',lifetime:'until-revoked'}
6
+ const call=vi.fn(async()=>({key:'save',selection,destination,status:'active'}))
7
+ const sdk=createBackgroundSaveFacade({call:call as any})
8
+ expect(await sdk.destination('save')).toEqual({key:'save',selection,destination,status:'active'})
9
+ expect(call).toHaveBeenCalledExactlyOnceWith('delegate.destination',{key:'save'})
10
+ call.mockResolvedValue({key:'save',selection,destination:{...destination,path:'/other'},status:'active'})
11
+ await expect(sdk.destination('save')).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
12
+ })
13
+ const ref={delegation_id:'mine',job_id:'original'}
14
+ test('operation cancellation keeps its bounded readable cause through the facade',async()=>{
15
+ const call=vi.fn(async()=>({job_id:'original',state:'canceled',code:'OPERATION_CANCELED',reason:'grant-revoked'}))
16
+ const sdk=createBackgroundSaveFacade({call:call as any})
17
+ expect(await sdk.cancel(ref)).toMatchObject({state:'canceled',reason:'grant-revoked'})
18
+ for(const reason of [123, 'x'.repeat(129), 'raw DSM response /path']){
19
+ call.mockResolvedValue({...await call(),reason} as any)
20
+ await expect(sdk.cancel(ref)).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
21
+ }
22
+ })
23
+ test('a copy cancel acknowledgement passes through unchanged for same-ref polling',async()=>{
24
+ const ack={job_id:'original',state:'copying',code:'CANCEL_REQUESTED',reason:'operation-canceled-by-user'}
25
+ const call=vi.fn(async()=>ack)
26
+ expect(await createBackgroundSaveFacade({call:call as any}).cancel(ref)).toEqual(ack)
27
+ expect(call).toHaveBeenCalledWith('delegate.operations.cancel',ref)
28
+ })
29
+ const grant={delegation_id:'mine',destination:'save',path:'/home/Exports',subject:{username:'normal1',uid:1205},lifetime:'until-revoked',revoked:false}
30
+ function fixture(){
31
+ const call=vi.fn(async(method:string)=>{
32
+ if(method==='delegate.status')return {state:'ready',disabled:false}
33
+ if(method==='delegate.list')return {delegations:[grant,{...grant,delegation_id:'revoked',revoked:true},{...grant,delegation_id:'legacy',lifetime:''}]}
34
+ if(method==='delegate.operations.get')return {job_id:'original',state:'committed',receipt:{path:'/home/Exports/report.csv',size:4}}
35
+ if(method==='delegate.operations.cancel')return {job_id:'original',state:'canceled'}
36
+ if(method==='delegate.revoke')return {revoked:true}
37
+ if(method==='delegate.disable')return {disabled:true}
38
+ if(method==='delegate.enable')return {disabled:false}
39
+ if(method==='delegate.select_destination')return {id:'mine',key:'save',path:'/home/Exports',username:'normal1',lifetime:'until-revoked'}
40
+ throw {code:'unknown_method'}
41
+ })
42
+ const jobs=vi.fn(async()=>[{...ref,name:'report.csv'}])
43
+ return {call,jobs,sdk:createBackgroundSaveFacade({call:call as any},jobs)}
44
+ }
45
+ test('W2 list is mapped to active permanent destinations without changing owner or legacy semantics',async()=>{
46
+ const {sdk,call}=fixture()
47
+ expect(await sdk.list()).toEqual([{id:'mine',key:'save',path:'/home/Exports',username:'normal1',lifetime:'until-revoked'}])
48
+ expect(call).toHaveBeenCalledExactlyOnceWith('delegate.list')
49
+ await sdk.selectDestination({key:'save',path:'/home/Exports'})
50
+ expect(call).toHaveBeenLastCalledWith('delegate.select_destination',{key:'save',path:'/home/Exports'})
51
+ })
52
+ test('App metadata is joined with protected status by the original two IDs, never a synthetic list route',async()=>{
53
+ const {sdk,call,jobs}=fixture()
54
+ expect(await sdk.operations()).toEqual([{...ref,name:'report.csv',state:'committed',receipt:{path:'/home/Exports/report.csv',size:4}}])
55
+ expect(jobs).toHaveBeenCalledOnce()
56
+ expect(call).toHaveBeenCalledExactlyOnceWith('delegate.operations.get',ref)
57
+ await sdk.cancel(ref);expect(call).toHaveBeenLastCalledWith('delegate.operations.cancel',ref)
58
+ await sdk.revoke({delegation_id:'mine'});expect(call).toHaveBeenLastCalledWith('delegate.revoke',{delegation_id:'mine'})
59
+ })
60
+ test('missing protected disabled status cannot be reported as enabled',async()=>{
61
+ const {sdk,call}=fixture();call.mockResolvedValue({state:'ready'} as any)
62
+ await expect(sdk.status()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
63
+ })
64
+ test('malformed grant and App metadata references cannot be coerced into valid IDs',async()=>{
65
+ const {sdk,call}=fixture()
66
+ call.mockResolvedValue({delegations:[{...grant,delegation_id:undefined}]} as any)
67
+ await expect(sdk.list()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
68
+ const invalid=createBackgroundSaveFacade({call:call as any},async()=>[{job_id:undefined,delegation_id:'mine',name:'report.csv'}] as any)
69
+ await expect(invalid.operations()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
70
+ expect(call).toHaveBeenCalledTimes(1)
71
+ })
72
+ test('malformed or mismatched operation evidence never becomes Saved',async()=>{
73
+ for(const value of [{job_id:'other',state:'committed',receipt:{path:'/home/a',size:1}},{job_id:'original',state:'committed'},{job_id:'original',state:'copying',receipt:{path:'/home/a',size:1}},{job_id:'original',state:'committed',receipt:{path:'/home/a',size:-1}}]){
74
+ const {sdk,call}=fixture();call.mockResolvedValue(value as any)
75
+ await expect(sdk.operations()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
76
+ }
77
+ })
78
+ test('read timeout never admits or resubmits an operation',async()=>{
79
+ const {sdk,call}=fixture();call.mockRejectedValue({code:'EXECUTOR_TIMEOUT'})
80
+ await expect(sdk.operations()).rejects.toMatchObject({code:'EXECUTOR_TIMEOUT'})
81
+ expect(call).toHaveBeenCalledExactlyOnceWith('delegate.operations.get',ref)
82
+ })
83
+ test('disable and enable use authenticated protected routes, not mailbox controls',async()=>{
84
+ const {sdk,call}=fixture()
85
+ expect(await sdk.status()).toEqual({disabled:false})
86
+ expect(await sdk.disable()).toEqual({disabled:true});expect(call).toHaveBeenLastCalledWith('delegate.disable')
87
+ expect(await sdk.enable()).toEqual({disabled:false});expect(call).toHaveBeenLastCalledWith('delegate.enable')
88
+ call.mockResolvedValue({} as any);await expect(sdk.disable()).rejects.toMatchObject({code:'EXECUTOR_PROTOCOL'})
89
+ })
90
+ test('the default facade lists backend mirrors but ignores their committed claim until live receipt',async()=>{
91
+ const {call}=fixture()
92
+ call.mockImplementation(async(method:string)=>{
93
+ if(method==='delegate.operations.list')return {operations:[{...ref,name:'report.csv',state:'committed'}]} as any
94
+ if(method==='delegate.operations.get')return {job_id:'original',state:'uncertain'} as any
95
+ throw {code:'unexpected'}
96
+ })
97
+ const sdk=createBackgroundSaveFacade({call:call as any})
98
+ expect(await sdk.operations()).toEqual([{...ref,name:'report.csv',state:'uncertain'}])
99
+ expect(call.mock.calls).toEqual([['delegate.operations.list'],['delegate.operations.get',ref]])
100
+ })
@@ -0,0 +1,85 @@
1
+ import type {Context} from '../context'
2
+ import type {BackgroundSaveFacade,DestinationGrant,DestinationState,ScopeSelection,OperationRef,OperationResult,SaveOperation} from './long-running'
3
+ // Supply an owner-bound App backend view, not browser storage or an executor list.
4
+ // Producers persist these references alongside their own business job metadata.
5
+ export interface AppSaveJob extends OperationRef {name:string;refusal?:string}
6
+ const token=/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/
7
+ const id=(value:unknown):value is string=>typeof value==='string'&&token.test(value)
8
+ const validRef=(ref:OperationRef)=>ref&&id(ref.delegation_id)&&id(ref.job_id)
9
+ const path=(value:unknown):value is string=>typeof value==='string'&&value.startsWith('/')&&value!=='/'&&!value.includes('\0')&&!value.slice(1).split('/').some(part=>!part||part==='.'||part==='..')
10
+ function protocol():never{throw {code:'EXECUTOR_PROTOCOL'}}
11
+ function validSelection(value:ScopeSelection){return value&&Number.isSafeInteger(value.generation)&&value.generation>=0&&path(value.root)&&(value.scope==='home'?value.root==='/home':value.scope.startsWith('shared:')&&value.root==='/'+value.scope.slice(7))}
12
+ function result(value:OperationResult,ref:OperationRef):OperationResult{
13
+ if(!value||value.job_id!==ref.job_id||!['queued','copying','committed','canceled','interrupted','uncertain'].includes(value.state))protocol()
14
+ if(value.state==='committed'){
15
+ if(!value.receipt||!path(value.receipt.path)||!Number.isSafeInteger(value.receipt.size)||value.receipt.size<0||value.receipt.size>16*1024**3)protocol()
16
+ }else if(value.receipt!==undefined)protocol()
17
+ if(value.reason!==undefined&&(typeof value.reason!=='string'||!/^[A-Za-z][A-Za-z0-9_-]{0,127}$/.test(value.reason)))protocol()
18
+ return {job_id:value.job_id,state:value.state,...(value.receipt?{receipt:{...value.receipt}}:{}),...(value.code?{code:value.code}:{}),...(value.reason?{reason:value.reason}:{})}
19
+ }
20
+ /** Bind the portable UI facade to W2's existing authenticated routes. */
21
+ export function createBackgroundSaveFacade(ctx:Pick<Context,'call'>,jobs:()=>Promise<readonly AppSaveJob[]>=async()=>{
22
+ const out=await ctx.call<{operations:AppSaveJob[]}>('delegate.operations.list')
23
+ if(!Array.isArray(out?.operations))protocol()
24
+ return out.operations
25
+ }):BackgroundSaveFacade{
26
+ const operation=async(method:string,ref:OperationRef)=>{
27
+ if(!validRef(ref))protocol()
28
+ return result(await ctx.call<OperationResult>(method,{delegation_id:ref.delegation_id,job_id:ref.job_id}),ref)
29
+ }
30
+ return {
31
+ async destination(key){
32
+ if(!id(key))protocol()
33
+ const out=await ctx.call<DestinationState>('delegate.destination',{key})
34
+ if(!out||out.key!==key||!['active','missing','reselection-required','needs-scope-confirmation','disabled'].includes(out.status)||out.selection!==null&&!validSelection(out.selection))protocol()
35
+ if(out.status==='active'){
36
+ const g=out.destination,s=out.selection
37
+ if(!g||!s||!id(g.id)||g.key!==key||g.lifetime!=='until-revoked'||!g.username||!path(g.path)||!(g.path===s.root||g.path.startsWith(s.root+'/')))protocol()
38
+ }else if(out.destination!==null)protocol()
39
+ return out
40
+ },
41
+ async exportExample(input){
42
+ if(!id(input.key)||!id(input.job_id))protocol()
43
+ const out=await ctx.call<OperationRef>('delegate.example.export',{key:input.key,job_id:input.job_id})
44
+ if(!validRef(out)||out.job_id!==input.job_id)protocol()
45
+ return out
46
+ },
47
+ selectDestination:input=>ctx.call<DestinationGrant>('delegate.select_destination',{key:input.key,path:input.path}),
48
+ async status(){
49
+ const value=await ctx.call<{state:string;disabled?:boolean;scope_mode?:string;preselection?:string;selection?:ScopeSelection|null;mismatch?:boolean}>('delegate.status')
50
+ // Capability readiness and the protected admission switch are independent.
51
+ if(!['ready','disabled','needs-scope-confirmation'].includes(value?.state))throw {code:'EXECUTOR_UNAVAILABLE'}
52
+ if(typeof value?.disabled!=='boolean')protocol()
53
+ if(value.selection&&!validSelection(value.selection))protocol()
54
+ return {disabled:value.disabled,...(value.scope_mode?{scopeMode:value.scope_mode,selection:value.selection??null,preselection:value.preselection,mismatch:value.mismatch}:{} )}
55
+ },
56
+ async list(){
57
+ const value=await ctx.call<{delegations:Array<{delegation_id:string;destination:string;path:string;subject:{username:string};lifetime:string;revoked:boolean}>}>('delegate.list')
58
+ if(!Array.isArray(value?.delegations))protocol()
59
+ return value.delegations.filter(g=>g.lifetime==='until-revoked'&&!g.revoked).map(g=>{
60
+ if(!id(g.delegation_id)||typeof g.destination!=='string'||!g.destination||!path(g.path)||typeof g.subject?.username!=='string'||!g.subject.username)protocol()
61
+ return {id:g.delegation_id,key:g.destination,path:g.path,username:g.subject.username,lifetime:'until-revoked' as const}
62
+ })
63
+ },
64
+ async operations(){
65
+ const metadata=await jobs()
66
+ return Promise.all(metadata.map(async job=>{
67
+ if(typeof job?.name!=='string'||!job.name||!validRef(job))protocol()
68
+ const ref={delegation_id:job.delegation_id,job_id:job.job_id}
69
+ try{return {...ref,name:job.name,...await operation('delegate.operations.get',ref)} satisfies SaveOperation}
70
+ catch(e){
71
+ if(job.refusal&&(e as {code?:string})?.code==='operation-not-found')return {...ref,name:job.name,state:'interrupted',code:job.refusal} satisfies SaveOperation
72
+ throw e
73
+ }
74
+ }))
75
+ },
76
+ async revoke(input){
77
+ const out=await ctx.call<{revoked:boolean}>('delegate.revoke',{delegation_id:input.delegation_id})
78
+ if(out?.revoked!==true)protocol()
79
+ return {revoked:true}
80
+ },
81
+ cancel:ref=>operation('delegate.operations.cancel',ref),
82
+ async disable(){const out=await ctx.call<{disabled:boolean}>('delegate.disable');if(out?.disabled!==true)protocol();return {disabled:true}},
83
+ async enable(){const out=await ctx.call<{disabled:boolean}>('delegate.enable');if(out?.disabled!==false)protocol();return {disabled:false}},
84
+ }
85
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "title": "Dateizugriff im Hintergrund",
3
+ "help": "Downloads laufen weiter, nachdem Sie diese App schließen. Jeder Auftrag darf bis zu einer Stunde lang nur in den ausgewählten Ordner schreiben. Vorhandene Dateien werden nie ersetzt.",
4
+ "setupTitle": "Administratoreinrichtung",
5
+ "setupHelp": "Genehmigen Sie diese App einmal, damit sie Dateiaufträge abschließen kann, während Benutzer offline sind.",
6
+ "password": "DSM-Passwort",
7
+ "authorize": "Dateizugriff im Hintergrund genehmigen",
8
+ "confirmShare": "Freigegebenen Ordner bestätigen",
9
+ "scopeNeeds": "Der freigegebene Ordner muss bestätigt werden. Wählen Sie unten einen freigegebenen Ordner und bestätigen Sie ihn mit Ihrem DSM-Passwort.",
10
+ "ready": "Der Dateizugriff im Hintergrund ist bereit.",
11
+ "needs": "Ein Administrator muss den Dateizugriff im Hintergrund genehmigen.",
12
+ "blocked": "Der Dateizugriff im Hintergrund ist nicht verfügbar. Bitten Sie einen Administrator, die App-Einrichtung zu prüfen.",
13
+ "refresh": "Aktualisieren",
14
+ "revoke": "Widerrufen",
15
+ "cancel": "Auftrag abbrechen",
16
+ "noDelegations": "Sie haben keine aktiven Delegierungen.",
17
+ "delegations": "Meine Delegierungen",
18
+ "expires": "Ablauf",
19
+ "downloads": "Meine Downloads",
20
+ "noJobs": "Sie haben keine Download-Aufträge.",
21
+ "url": "Download-URL",
22
+ "name": "Dateiname",
23
+ "choose": "Ordner auswählen",
24
+ "submit": "In meinen Ordner herunterladen",
25
+ "destination": "Ziel",
26
+ "pending": "Auftrag abgebrochen. Der Widerruf steht noch aus; die App versucht es erneut.",
27
+ "error": "Die Aktion konnte nicht abgeschlossen werden. Aktualisieren Sie die Ansicht und versuchen Sie es erneut. Wenn der Fehler bestehen bleibt, wenden Sie sich an Ihren Administrator.",
28
+ "setupError": "Die Genehmigung konnte nicht abgeschlossen werden. Geben Sie Ihr DSM-Passwort erneut ein und versuchen Sie es noch einmal, oder prüfen Sie die App-Einrichtung.",
29
+ "denied": "Der Zugriff wurde verweigert. Prüfen Sie Ihre Ordnerberechtigungen und versuchen Sie es erneut.",
30
+ "session": "Melden Sie sich erneut bei DSM an und aktualisieren Sie dann die Ansicht.",
31
+ "size": "Downloads sind auf 64 MB und 60 Sekunden begrenzt.",
32
+ "queued": "In Warteschlange",
33
+ "running": "Wird heruntergeladen",
34
+ "succeeded": "Gespeichert",
35
+ "failed": "Fehlgeschlagen",
36
+ "canceled": "Abgebrochen",
37
+ "interrupted": "Unterbrochen",
38
+ "homeScope": "Home-Ordner jedes Benutzers",
39
+ "scopeLabel": "Bereich für Speichern im Hintergrund",
40
+ "protectedScope": "Bestätigter Bereich",
41
+ "preselection": "Vorauswahl bei der Installation",
42
+ "preselectionHelp": "Die Vorauswahl bei der Installation ist nur ein Vorschlag. Bestätigen Sie diesen Bereich mit Ihrem DSM-Passwort, wenn Sie den Hintergrundzugriff aktivieren.",
43
+ "scopeMismatch": "Die Vorauswahl bei der Installation weicht vom bestätigten Bereich ab. Speichervorgänge im Hintergrund folgen dem bestätigten Bereich.",
44
+ "scopeChangeHelp": "Eine Änderung des Bereichs widerruft vorhandene Ziele und bricht ausstehende Speichervorgänge ab. Benutzer müssen ihre Ziele neu auswählen. Bereits gespeicherte Dateien bleiben erhalten.",
45
+ "confirmScope": "Bereichsänderung bestätigen"
46
+ }
@@ -5,6 +5,8 @@
5
5
  "setupHelp": "Approve this app once to let it complete file jobs while users are offline.",
6
6
  "password": "DSM password",
7
7
  "authorize": "Approve background file access",
8
+ "confirmShare": "Confirm share",
9
+ "scopeNeeds": "Share needs confirmation. Choose a share below and confirm it with your DSM password.",
8
10
  "ready": "Background file access is ready.",
9
11
  "needs": "An administrator must approve background file access.",
10
12
  "blocked": "Background file access is unavailable. Ask an administrator to check the app setup.",
@@ -26,11 +28,19 @@
26
28
  "setupError": "Approval could not be completed. Re-enter your DSM password and try again, or check the app setup.",
27
29
  "denied": "Access was refused. Check your folder permissions and try again.",
28
30
  "session": "Sign in to DSM again, then refresh.",
29
- "size": "Downloads are limited to 64 MiB and 60 seconds.",
31
+ "size": "Downloads are limited to 64 MB and 60 seconds.",
30
32
  "queued": "Queued",
31
33
  "running": "Downloading",
32
34
  "succeeded": "Saved",
33
35
  "failed": "Failed",
34
36
  "canceled": "Canceled",
35
- "interrupted": "Interrupted"
37
+ "interrupted": "Interrupted",
38
+ "homeScope": "Each user’s home folder",
39
+ "scopeLabel": "Background save scope",
40
+ "protectedScope": "Confirmed scope",
41
+ "preselection": "Installer pre-selection",
42
+ "preselectionHelp": "The installer pre-selection is a suggestion. Confirm this scope with your DSM password when enabling background access.",
43
+ "scopeMismatch": "The installer pre-selection differs from the confirmed scope. Background saves follow the confirmed scope.",
44
+ "scopeChangeHelp": "Changing the scope revokes existing destinations and cancels pending saves. Users must choose destinations again. Saved files remain.",
45
+ "confirmScope": "Confirm scope change"
36
46
  }
@@ -0,0 +1,46 @@
1
+ {
2
+ "title": "Acceso a archivos en segundo plano",
3
+ "help": "Las descargas continúan después de cerrar esta aplicación. Cada tarea solo puede escribir en la carpeta seleccionada, durante una hora como máximo. Los archivos existentes nunca se reemplazan.",
4
+ "setupTitle": "Configuración del administrador",
5
+ "setupHelp": "Apruebe esta aplicación una vez para que complete tareas de archivos mientras los usuarios están desconectados.",
6
+ "password": "Contraseña de DSM",
7
+ "authorize": "Aprobar el acceso a archivos en segundo plano",
8
+ "confirmShare": "Confirmar carpeta compartida",
9
+ "scopeNeeds": "La carpeta compartida debe confirmarse. Elija una carpeta compartida abajo y confírmela con su contraseña de DSM.",
10
+ "ready": "El acceso a archivos en segundo plano está listo.",
11
+ "needs": "Un administrador debe aprobar el acceso a archivos en segundo plano.",
12
+ "blocked": "El acceso a archivos en segundo plano no está disponible. Pida a un administrador que revise la configuración de la aplicación.",
13
+ "refresh": "Actualizar",
14
+ "revoke": "Revocar",
15
+ "cancel": "Cancelar tarea",
16
+ "noDelegations": "No tiene delegaciones activas.",
17
+ "delegations": "Mis delegaciones",
18
+ "expires": "Caducidad",
19
+ "downloads": "Mis descargas",
20
+ "noJobs": "No tiene tareas de descarga.",
21
+ "url": "URL de descarga",
22
+ "name": "Nombre de archivo",
23
+ "choose": "Elegir carpeta",
24
+ "submit": "Descargar en mi carpeta",
25
+ "destination": "Destino",
26
+ "pending": "Tarea cancelada. La revocación está pendiente; la aplicación volverá a intentarlo.",
27
+ "error": "No se pudo completar la acción. Actualice e inténtelo de nuevo. Si sigue fallando, póngase en contacto con su administrador.",
28
+ "setupError": "No se pudo completar la aprobación. Vuelva a introducir su contraseña de DSM e inténtelo de nuevo, o revise la configuración de la aplicación.",
29
+ "denied": "Se denegó el acceso. Compruebe los permisos de la carpeta e inténtelo de nuevo.",
30
+ "session": "Vuelva a iniciar sesión en DSM y luego actualice.",
31
+ "size": "Las descargas están limitadas a 64 MB y 60 segundos.",
32
+ "queued": "En cola",
33
+ "running": "Descargando",
34
+ "succeeded": "Guardado",
35
+ "failed": "Error",
36
+ "canceled": "Cancelado",
37
+ "interrupted": "Interrumpido",
38
+ "homeScope": "Carpeta home de cada usuario",
39
+ "scopeLabel": "Ámbito de guardado en segundo plano",
40
+ "protectedScope": "Ámbito confirmado",
41
+ "preselection": "Preselección de la instalación",
42
+ "preselectionHelp": "La preselección de la instalación es solo una sugerencia. Confirme este ámbito con su contraseña de DSM al activar el acceso en segundo plano.",
43
+ "scopeMismatch": "La preselección de la instalación difiere del ámbito confirmado. Los guardados en segundo plano siguen el ámbito confirmado.",
44
+ "scopeChangeHelp": "Cambiar el ámbito revoca los destinos existentes y cancela los guardados pendientes. Los usuarios deben elegir de nuevo sus destinos. Los archivos ya guardados se conservan.",
45
+ "confirmScope": "Confirmar cambio de ámbito"
46
+ }