@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
@@ -135,6 +135,9 @@ func (d *liveDSM) call(ctx context.Context, api, method, version, runner string,
135
135
  }
136
136
  b, err := d.Command.Exec(ctx, "/usr/syno/bin/synowebapi", args)
137
137
  if err != nil {
138
+ if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
139
+ return nil, err
140
+ }
138
141
  return nil, errors.New("DSM command")
139
142
  }
140
143
  return decodeEnvelope(b)
@@ -161,13 +164,21 @@ func (d *liveDSM) Account(ctx context.Context, name string) (Subject, error) {
161
164
  s.Expired = out.Users[0].Expired
162
165
  return s, nil
163
166
  }
164
- func (d *liveDSM) Allow(ctx context.Context, s Subject, app string) error {
167
+
168
+ // Allow is the executor's offline App Privileges decision. It follows the CGI
169
+ // EntryGate: administrators pass, admin-only refuses everyone else, and
170
+ // app-privilege requires DSM's own allow. Preview lists only effective allows
171
+ // (user, group and everyone rules), so an absent App is allowed only when the
172
+ // package declares no entry mode and DSM therefore never manages its App.
173
+ func (d *liveDSM) Allow(ctx context.Context, s Subject, app, entry string) error {
174
+ if !contains([]string{"", "admin-only", "app-privilege"}, entry) {
175
+ return errors.New("entry mode")
176
+ }
165
177
  data, err := d.call(ctx, "SYNO.Core.AppPriv.App", "preview", "2", "", map[string]any{"username": s.Username})
166
178
  if err != nil {
167
179
  return err
168
180
  }
169
- // DSM 7.x omits default-allowed Apps from preview (measured on vt-nas).
170
- // An explicit deny/custom entry or malformed/ambiguous response still refuses.
181
+ // An explicit deny/custom entry or malformed/ambiguous response refuses.
171
182
  var out struct {
172
183
  Apps *[]json.RawMessage `json:"applications"`
173
184
  }
@@ -193,7 +204,10 @@ func (d *liveDSM) Allow(ctx context.Context, s Subject, app string) error {
193
204
  if count > 1 {
194
205
  return errors.New("app ambiguous")
195
206
  }
196
- return nil
207
+ if entry == "" || s.Admin || entry == "app-privilege" && count == 1 {
208
+ return nil
209
+ }
210
+ return errors.New("app denied")
197
211
  }
198
212
  func (d *liveDSM) ScopeRoot(ctx context.Context, s Subject, scope string) (string, error) {
199
213
  if scope == "home" {
@@ -210,16 +224,15 @@ func (d *liveDSM) ScopeRoot(ctx context.Context, s Subject, scope string) (strin
210
224
  if err != nil {
211
225
  return "", err
212
226
  }
227
+ // DSM Share.get returns one share and its volume, not Share.list's array.
213
228
  var out struct {
214
- Shares []struct {
215
- Name string `json:"name"`
216
- Path string `json:"path"`
217
- } `json:"shares"`
229
+ Name string `json:"name"`
230
+ Volume string `json:"vol_path"`
218
231
  }
219
- if json.Unmarshal(data, &out) != nil || len(out.Shares) != 1 || out.Shares[0].Name != name || !cleanPath(out.Shares[0].Path) {
232
+ if json.Unmarshal(data, &out) != nil || out.Name != name || !validSelectedScope(scope) || !cleanPath(out.Volume) || out.Volume == "/" {
220
233
  return "", errors.New("share")
221
234
  }
222
- return filepath.EvalSymlinks(out.Shares[0].Path)
235
+ return filepath.EvalSymlinks(filepath.Join(out.Volume, out.Name))
223
236
  }
224
237
  func (d *liveDSM) API(ctx context.Context, runner, op string, p map[string]any) (json.RawMessage, error) {
225
238
  if runner == "" {
@@ -5,7 +5,10 @@ import (
5
5
  "errors"
6
6
  "net/http"
7
7
  "net/http/httptest"
8
+ "os"
9
+ "path/filepath"
8
10
  "reflect"
11
+ "strconv"
9
12
  "strings"
10
13
  "testing"
11
14
  )
@@ -24,6 +27,25 @@ type cmdStub struct {
24
27
  err error
25
28
  }
26
29
 
30
+ func TestDSMCommandDeadlineStaysDistinctFromDenial(t *testing.T) {
31
+ for _, cause := range []error{context.DeadlineExceeded, errors.New("explicit DSM refusal")} {
32
+ d := &liveDSM{Command: &cmdStub{err: cause}, Accounts: accountsStub{s: Subject{Username: "alice", UID: 1200, GID: 100, Groups: []int{100}}}}
33
+ _, err := d.ScopeRoot(context.Background(), Subject{Username: "alice"}, "shared:reports")
34
+ if errors.Is(err, context.DeadlineExceeded) != errors.Is(cause, context.DeadlineExceeded) {
35
+ t.Fatal("lost deadline identity", err)
36
+ }
37
+ e := &Executor{DSM: d, Policy: Policy{App: "app"}}
38
+ _, code := e.eligible(context.Background(), "alice", 1200, true)
39
+ want := "INELIGIBLE"
40
+ if errors.Is(cause, context.DeadlineExceeded) {
41
+ want = "EXECUTOR_PAUSED"
42
+ }
43
+ if code != want {
44
+ t.Fatal(code, want)
45
+ }
46
+ }
47
+ }
48
+
27
49
  func (c *cmdStub) Exec(ctx context.Context, p string, args []string) ([]byte, error) {
28
50
  c.path = p
29
51
  c.args = args
@@ -69,7 +91,7 @@ func TestDSMAppPrivilege(t *testing.T) {
69
91
  t.Run(tc.name, func(t *testing.T) {
70
92
  c := &cmdStub{reply: tc.reply}
71
93
  d := &liveDSM{Command: c}
72
- err := d.Allow(context.Background(), Subject{Username: "alice"}, "app")
94
+ err := d.Allow(context.Background(), Subject{Username: "alice"}, "app", "")
73
95
  if (err == nil) != tc.pass {
74
96
  t.Fatal(err)
75
97
  }
@@ -79,6 +101,41 @@ func TestDSMAppPrivilege(t *testing.T) {
79
101
  })
80
102
  }
81
103
  }
104
+
105
+ // Preview lists only effective allows, so an absent App means "not allowed"
106
+ // unless DSM never manages the App (no entry mode). Mirrors the CGI EntryGate.
107
+ func TestDSMAppPrivilegeByEntryMode(t *testing.T) {
108
+ const (
109
+ absent = `{"success":true,"data":{"applications":[{"app_id":"other","privilelge":"allow"}]}}`
110
+ allowed = `{"success":true,"data":{"applications":[{"app_id":"app","privilelge":"allow"}]}}`
111
+ denied = `{"success":true,"data":{"applications":[{"app_id":"app","privilelge":"deny"}]}}`
112
+ allowAll = `{"success":true,"data":{"applications":[{"app_id":"SYNO.ALLOW.ALL.APPLICATIONS","privilelge":"allow"}]}}`
113
+ malformed = `{"success":true,"data":{}}`
114
+ )
115
+ user, admin := Subject{Username: "alice"}, Subject{Username: "root-admin", Admin: true}
116
+ for _, tc := range []struct {
117
+ name, entry, reply string
118
+ s Subject
119
+ pass bool
120
+ }{
121
+ {"legacy absent", "", absent, user, true},
122
+ {"app-privilege absent", "app-privilege", absent, user, false},
123
+ {"app-privilege allowed", "app-privilege", allowed, user, true},
124
+ {"app-privilege denied", "app-privilege", denied, user, false},
125
+ {"app-privilege admin", "app-privilege", allowAll, admin, true},
126
+ {"app-privilege admin malformed", "app-privilege", malformed, admin, false},
127
+ {"admin-only user allowed rule", "admin-only", allowed, user, false},
128
+ {"admin-only admin", "admin-only", allowAll, admin, true},
129
+ {"unknown mode", "everyone", allowed, user, false},
130
+ } {
131
+ t.Run(tc.name, func(t *testing.T) {
132
+ d := &liveDSM{Command: &cmdStub{reply: tc.reply}}
133
+ if err := d.Allow(context.Background(), tc.s, "app", tc.entry); (err == nil) != tc.pass {
134
+ t.Fatal(err)
135
+ }
136
+ })
137
+ }
138
+ }
82
139
  func TestDSMVerify(t *testing.T) {
83
140
  for _, reply := range []string{`{"success":true,"data":{"username":"alice"}}`, `{"success":true,"data":{}}`, `{"success":false}`, strings.Repeat("x", maxRequest+1)} {
84
141
  srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -194,3 +251,40 @@ func TestDefaultAppPrivilegeNeverBypassesExplicitDenialOrDisabledAccount(t *test
194
251
  })
195
252
  }
196
253
  }
254
+
255
+ func TestDSMSharedScopeUsesMeasuredGetVolumeContract(t *testing.T) {
256
+ volume := t.TempDir()
257
+ volume, err := filepath.EvalSymlinks(volume)
258
+ if err != nil {
259
+ t.Fatal(err)
260
+ }
261
+ share := filepath.Join(volume, "reports")
262
+ if err := os.Mkdir(share, 0700); err != nil {
263
+ t.Fatal(err)
264
+ }
265
+ for _, tc := range []struct {
266
+ name, data string
267
+ pass bool
268
+ }{
269
+ {"DSM single share", `{"name":"reports","vol_path":` + strconv.Quote(volume) + `,"desc":"test","uuid":"fixture"}`, true},
270
+ {"different share", `{"name":"other","vol_path":` + strconv.Quote(volume) + `}`, false},
271
+ {"missing volume", `{"name":"reports"}`, false},
272
+ {"relative volume", `{"name":"reports","vol_path":"relative"}`, false},
273
+ {"unclean volume", `{"name":"reports","vol_path":"/volume1/../etc"}`, false},
274
+ {"root volume", `{"name":"reports","vol_path":"/"}`, false},
275
+ {"null", `null`, false},
276
+ {"invented list contract", `{"shares":[{"name":"reports","path":` + strconv.Quote(share) + `}]}`, false},
277
+ } {
278
+ t.Run(tc.name, func(t *testing.T) {
279
+ cmd := &cmdStub{reply: `{"success":true,"data":` + tc.data + `}`}
280
+ d := &liveDSM{Command: cmd}
281
+ root, err := d.ScopeRoot(context.Background(), Subject{}, "shared:reports")
282
+ if (err == nil) != tc.pass || tc.pass && root != share {
283
+ t.Fatalf("root=%q err=%v", root, err)
284
+ }
285
+ if !reflect.DeepEqual(cmd.args, []string{"--exec", "api=SYNO.Core.Share", "method=get", "version=1", `name="reports"`}) {
286
+ t.Fatal(cmd.args)
287
+ }
288
+ })
289
+ }
290
+ }
@@ -22,17 +22,21 @@ import (
22
22
  const version = "1"
23
23
  const maxRequest = 64 << 10
24
24
 
25
- var allOps = []string{"status", "delegate.create", "delegate.list", "delegate.revoke", "files.list", "files.info", "files.createFolder", "files.checkPermission", "files.write", "root.run"}
25
+ var allOps = []string{"status", "delegate.create", "delegate.list", "delegate.revoke", "files.list", "files.info", "files.createFolder", "files.checkPermission", "files.write", "files.moveFromStaging", "files.operationStatus", "files.cancelOperation", "delegate.disable", "delegate.enable", "delegate.audit", "root.run"}
26
26
  var tokenRE = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$`)
27
+ var destinationRE = regexp.MustCompile(`^[a-z][a-z0-9_-]{0,63}$`)
27
28
  var idRE = regexp.MustCompile(`^[a-f0-9]{64}$`)
28
29
 
29
30
  type Policy struct {
30
- Version int `json:"v"`
31
- Package string `json:"package"`
32
- App string `json:"app"`
33
- Scope string `json:"scope,omitempty"`
34
- Operations []string `json:"operations"`
35
- Root map[string]string `json:"root,omitempty"`
31
+ Version int `json:"v"`
32
+ Package string `json:"package"`
33
+ App string `json:"app"`
34
+ Entry string `json:"entry,omitempty"`
35
+ Scope string `json:"scope,omitempty"`
36
+ Operations []string `json:"operations"`
37
+ Destinations []string `json:"destinations,omitempty"`
38
+ Lifetime string `json:"lifetime,omitempty"`
39
+ Root map[string]string `json:"root,omitempty"`
36
40
  }
37
41
  type Subject struct {
38
42
  Username string `json:"username"`
@@ -44,16 +48,20 @@ type Subject struct {
44
48
  Admin bool `json:"admin"`
45
49
  }
46
50
  type Delegation struct {
47
- ID string `json:"delegation_id"`
48
- Package string `json:"package"`
49
- Subject Subject `json:"subject"`
50
- Job string `json:"job_id"`
51
- Ops []string `json:"ops"`
52
- Scope string `json:"scope"`
53
- Path string `json:"path"`
54
- Root string `json:"root"`
55
- Expires time.Time `json:"expires_at"`
56
- Revoked bool `json:"revoked"`
51
+ ScopeGeneration uint64 `json:"scope_generation,omitempty"`
52
+ Lifetime string `json:"lifetime,omitempty"`
53
+ DestinationKey string `json:"destination,omitempty"`
54
+ Directory directoryIdentity `json:"directory,omitempty"`
55
+ ID string `json:"delegation_id"`
56
+ Package string `json:"package"`
57
+ Subject Subject `json:"subject"`
58
+ Job string `json:"job_id"`
59
+ Ops []string `json:"ops"`
60
+ Scope string `json:"scope"`
61
+ Path string `json:"path"`
62
+ Root string `json:"root"`
63
+ Expires time.Time `json:"expires_at"`
64
+ Revoked bool `json:"revoked"`
57
65
  }
58
66
  type Auth struct {
59
67
  Cookie string `json:"dsm_cookie,omitempty"`
@@ -95,20 +103,23 @@ type Response struct {
95
103
  Message string `json:"message,omitempty"`
96
104
  }
97
105
  type Params struct {
98
- Job string `json:"job_id,omitempty"`
99
- Ops []string `json:"ops,omitempty"`
100
- Scope string `json:"scope,omitempty"`
101
- Path string `json:"path,omitempty"`
102
- TTL int64 `json:"ttl_seconds,omitempty"`
103
- ID string `json:"delegation_id,omitempty"`
104
- Staging string `json:"staging,omitempty"`
105
- Dest string `json:"dest_dir,omitempty"`
106
- Name string `json:"name,omitempty"`
106
+ Lifetime string `json:"lifetime,omitempty"`
107
+ DestinationKey string `json:"destination,omitempty"`
108
+ Size int64 `json:"size,omitempty"`
109
+ Job string `json:"job_id,omitempty"`
110
+ Ops []string `json:"ops,omitempty"`
111
+ Scope string `json:"scope,omitempty"`
112
+ Path string `json:"path,omitempty"`
113
+ TTL int64 `json:"ttl_seconds,omitempty"`
114
+ ID string `json:"delegation_id,omitempty"`
115
+ Staging string `json:"staging,omitempty"`
116
+ Dest string `json:"dest_dir,omitempty"`
117
+ Name string `json:"name,omitempty"`
107
118
  }
108
119
  type DSM interface {
109
120
  Verify(context.Context, string, string) (string, error)
110
121
  Account(context.Context, string) (Subject, error)
111
- Allow(context.Context, Subject, string) error
122
+ Allow(context.Context, Subject, string, string) error
112
123
  ScopeRoot(context.Context, Subject, string) (string, error)
113
124
  API(context.Context, string, string, map[string]any) (json.RawMessage, error)
114
125
  }
@@ -128,7 +139,10 @@ type Executor struct {
128
139
  Store *Store
129
140
  Now func() time.Time
130
141
  ExecutorDigest, PolicyDigest string
131
- mu sync.Mutex // Serializes execution with revoke, including already accepted requests.
142
+ legacyCalls map[string][]chan struct{}
143
+ copySlot chan struct{}
144
+ commitMu sync.Mutex // Durable admission/revoke/disable and bounded publish only.
145
+ async *operationRuntime
132
146
  }
133
147
 
134
148
  func deny(code string) Response {
@@ -231,7 +245,7 @@ func uniqueValue(d *json.Decoder, canonical bool) error {
231
245
  }
232
246
  func (e *Executor) Connection(parent context.Context, c net.Conn, uid int, peer func(net.Conn) (int, error)) {
233
247
  got, err := peer(c)
234
- if err != nil || got != uid {
248
+ if err != nil || (got != uid && got != 0) {
235
249
  return
236
250
  }
237
251
  c.SetDeadline(time.Now().Add(15 * time.Second))
@@ -249,22 +263,36 @@ func (e *Executor) Connection(parent context.Context, c net.Conn, uid int, peer
249
263
  } else {
250
264
  ctx, cancel := context.WithTimeout(parent, 30*time.Second)
251
265
  defer cancel()
252
- out = e.Handle(ctx, req)
266
+ out = e.handlePeer(ctx, req, got, uid)
253
267
  }
254
268
  }
255
269
  c.SetWriteDeadline(time.Now().Add(5 * time.Second))
256
270
  json.NewEncoder(c).Encode(out)
257
271
  }
258
272
  func (e *Executor) eligible(ctx context.Context, name string, uid int, files bool) (Subject, string) {
273
+ moveValidationStage(ctx, "account")
259
274
  s, err := e.DSM.Account(ctx, name)
260
- if err != nil || s.Username != name || s.Expired != "normal" || s.Admin || s.UID < 1024 || s.GID < 0 || len(s.Groups) == 0 || (uid >= 0 && s.UID != uid) {
275
+ if moveValidationDeadline(ctx, err) {
276
+ return s, "EXECUTOR_PAUSED"
277
+ }
278
+ if err != nil || s.Username != name || s.Expired != "normal" || s.UID < 1024 || s.GID < 0 || len(s.Groups) == 0 || (uid >= 0 && s.UID != uid) {
261
279
  return Subject{}, "INELIGIBLE"
262
280
  }
263
- if e.DSM.Allow(ctx, s, e.Policy.App) != nil {
281
+ moveValidationStage(ctx, "app-privilege")
282
+ if err := e.DSM.Allow(ctx, s, e.Policy.App, e.Policy.Entry); err != nil {
283
+ if moveValidationDeadline(ctx, err) {
284
+ return s, "EXECUTOR_PAUSED"
285
+ }
264
286
  return Subject{}, "APP_DENIED"
265
287
  }
266
- if files && e.DSM.Allow(ctx, s, "SYNO.SDS.App.FileStation3.Instance") != nil {
267
- return Subject{}, "APP_DENIED"
288
+ moveValidationStage(ctx, "file-station-privilege")
289
+ if files {
290
+ if err := e.DSM.Allow(ctx, s, "SYNO.SDS.App.FileStation3.Instance", ""); err != nil {
291
+ if moveValidationDeadline(ctx, err) {
292
+ return s, "EXECUTOR_PAUSED"
293
+ }
294
+ return Subject{}, "APP_DENIED"
295
+ }
268
296
  }
269
297
  return s, ""
270
298
  }
@@ -283,8 +311,9 @@ func (e *Executor) verified(ctx context.Context, auth Auth) (Subject, string) {
283
311
  return s, ""
284
312
  }
285
313
  func (e *Executor) Handle(ctx context.Context, r Request) Response {
286
- e.mu.Lock()
287
- defer e.mu.Unlock()
314
+ if isExtension(r.Op) {
315
+ return e.handleExtension(ctx, r)
316
+ }
288
317
  if ctx.Err() != nil {
289
318
  return deny("EXECUTOR_PAUSED")
290
319
  }
@@ -309,17 +338,55 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
309
338
  if json.Unmarshal(r.Params, &keys) != nil {
310
339
  return deny("INVALID_REQUEST")
311
340
  }
312
- allowed := map[string]string{"status": "", "delegate.create": "job_id ops scope path ttl_seconds", "delegate.list": "", "delegate.revoke": "delegation_id job_id", "files.list": "delegation_id job_id path", "files.info": "delegation_id job_id path", "files.createFolder": "delegation_id job_id path name", "files.checkPermission": "delegation_id job_id path", "files.write": "delegation_id job_id staging dest_dir name", "root.run": "name"}
341
+ allowed := map[string]string{"status": "", "delegate.create": "job_id ops scope path ttl_seconds destination lifetime", "delegate.list": "", "delegate.revoke": "delegation_id job_id", "files.list": "delegation_id job_id path", "files.info": "delegation_id job_id path", "files.createFolder": "delegation_id job_id path name", "files.checkPermission": "delegation_id job_id path", "files.write": "delegation_id job_id staging dest_dir name", "root.run": "name"}
313
342
  for k := range keys {
314
343
  if !contains(strings.Fields(allowed[r.Op]), k) {
315
344
  return deny("INVALID_REQUEST")
316
345
  }
317
346
  }
347
+ if r.Op == "delegate.create" || strings.HasPrefix(r.Op, "files.") {
348
+ e.commitMu.Lock()
349
+ disabled, err := e.Store.Disabled()
350
+ e.commitMu.Unlock()
351
+ if err != nil {
352
+ return deny("STORAGE_ERROR")
353
+ }
354
+ if disabled {
355
+ return deny("disabled")
356
+ }
357
+ }
318
358
  switch r.Op {
319
359
  case "status":
320
- return success(map[string]any{"executor_digest": e.ExecutorDigest, "policy_digest": e.PolicyDigest, "version": version, "ready": true})
360
+ e.commitMu.Lock()
361
+ defer e.commitMu.Unlock()
362
+ disabled, err := e.Store.Disabled()
363
+ out := map[string]any{"executor_digest": e.ExecutorDigest, "policy_digest": e.PolicyDigest, "version": version, "ready": err == nil, "disabled": disabled}
364
+ if e.Policy.Scope == installSelectedScope {
365
+ state, code := e.selectionLocked()
366
+ out["scope_state"] = code
367
+ out["selection"] = nil
368
+ if code == "" {
369
+ out["selection"] = map[string]any{"scope": state.Scope, "root": scopeVirtual(state.Scope), "generation": state.Generation}
370
+ }
371
+ if code == "STORAGE_ERROR" {
372
+ out["ready"] = false
373
+ }
374
+ }
375
+ return success(out)
321
376
  case "delegate.create":
322
- if !tokenRE.MatchString(p.Job) || p.TTL <= 0 || p.TTL > 86400*30 || len(p.Ops) == 0 || p.Scope != e.Policy.Scope || !cleanPath(p.Path) {
377
+ long := p.Lifetime == "until-revoked"
378
+ if long {
379
+ if _, ok := keys["job_id"]; ok {
380
+ return deny("INVALID_REQUEST")
381
+ }
382
+ if _, ok := keys["ttl_seconds"]; ok {
383
+ return deny("INVALID_REQUEST")
384
+ }
385
+ if !contains(e.Policy.Destinations, p.DestinationKey) {
386
+ return deny("invalid-destination")
387
+ }
388
+ }
389
+ if (long && (e.Policy.Lifetime != p.Lifetime || !contains(e.Policy.Destinations, p.DestinationKey) || p.Job != "" || p.TTL != 0 || len(p.Ops) != 1 || p.Ops[0] != "files.moveFromStaging")) || (!long && (p.Lifetime != "" || p.DestinationKey != "" || !tokenRE.MatchString(p.Job) || p.TTL <= 0 || p.TTL > 86400*30 || contains(p.Ops, "files.moveFromStaging"))) || len(p.Ops) == 0 || p.Scope != e.Policy.Scope || !cleanPath(p.Path) {
323
390
  return deny("INVALID_REQUEST")
324
391
  }
325
392
  for _, op := range p.Ops {
@@ -335,17 +402,35 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
335
402
  if code != "" {
336
403
  return deny(code)
337
404
  }
338
- root, err := e.DSM.ScopeRoot(ctx, s, p.Scope)
405
+ selection, selectionCode := e.selection()
406
+ if selectionCode != "" {
407
+ return deny(selectionCode)
408
+ }
409
+ root, err := e.DSM.ScopeRoot(ctx, s, selection.Scope)
339
410
  if err != nil {
340
411
  return deny("SCOPE_DENIED")
341
412
  }
342
- d := Delegation{Package: e.Policy.Package, Subject: s, Job: p.Job, Ops: p.Ops, Scope: p.Scope, Path: scopeVirtual(p.Scope), Root: root, Expires: e.Now().UTC().Add(time.Duration(p.TTL) * time.Second)}
343
- real, err := scopedPath(d, p.Path)
413
+ d := Delegation{Package: e.Policy.Package, Subject: s, Job: p.Job, Ops: p.Ops, Scope: selection.Scope, ScopeGeneration: selection.Generation, Path: scopeVirtual(selection.Scope), Root: root, Expires: e.Now().UTC().Add(time.Duration(p.TTL) * time.Second)}
414
+ var real string
415
+ if long {
416
+ real, err = mappedDirectory(d, p.Path)
417
+ } else {
418
+ real, err = scopedPath(d, p.Path)
419
+ }
344
420
  if err != nil {
345
421
  return deny("SCOPE_DENIED")
346
422
  }
347
423
  d.Root = real
348
424
  d.Path = p.Path
425
+ if long {
426
+ d.Lifetime = p.Lifetime
427
+ d.DestinationKey = p.DestinationKey
428
+ d.Expires = time.Time{}
429
+ d.Directory, err = bindDirectory(real)
430
+ if err != nil {
431
+ return deny("SCOPE_DENIED")
432
+ }
433
+ }
349
434
  // Permission creation is checked as the subject, never SYSTEM_ADMIN.
350
435
  if _, err = e.DSM.API(ctx, s.Username, "files.checkPermission", map[string]any{"path": p.Path}); err != nil {
351
436
  return deny("PERMISSION_DENIED")
@@ -355,9 +440,49 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
355
440
  return deny("INTERNAL")
356
441
  }
357
442
  d.ID = hex.EncodeToString(id)
358
- if e.Store.Put(d) != nil {
443
+ e.commitMu.Lock()
444
+ disabled, disableErr := e.Store.Disabled()
445
+ if !e.scopeMatchesLocked(d) {
446
+ e.commitMu.Unlock()
447
+ return deny("SCOPE_DENIED")
448
+ }
449
+ if disableErr != nil || disabled {
450
+ e.commitMu.Unlock()
451
+ return deny("disabled")
452
+ }
453
+ putErr := e.auditLocked(s, "delegate.create", "", d.Path, "admitting")
454
+ // A destination key denotes one current choice for this person. Changing
455
+ // it cancels its previous grant under the same publish fence.
456
+ if putErr == nil && long {
457
+ var previous []Delegation
458
+ previous, putErr = e.Store.List()
459
+ for _, prior := range previous {
460
+ if putErr != nil {
461
+ break
462
+ }
463
+ if prior.Package != d.Package || prior.Subject.UID != s.UID || prior.Subject.Username != s.Username || prior.Lifetime != "until-revoked" || prior.DestinationKey != d.DestinationKey || prior.Revoked {
464
+ continue
465
+ }
466
+ putErr = e.auditLocked(s, "delegate.revoke", prior.ID, prior.Path, "destination-replaced")
467
+ if putErr == nil {
468
+ prior.Revoked = true
469
+ putErr = e.Store.Put(prior)
470
+ }
471
+ if putErr == nil {
472
+ e.cancelGrantLocked(prior.ID, "destination-replaced")
473
+ }
474
+ }
475
+ }
476
+ if putErr == nil {
477
+ putErr = e.Store.Put(d)
478
+ }
479
+ e.commitMu.Unlock()
480
+ if putErr != nil {
359
481
  return deny("STORAGE_ERROR")
360
482
  }
483
+ if long {
484
+ return success(publicDelegation(d))
485
+ }
361
486
  return success(map[string]any{"delegation_id": d.ID, "subject": publicSubject(s), "expires_at": d.Expires})
362
487
  case "delegate.list":
363
488
  s, code := e.verified(ctx, r.Auth)
@@ -371,6 +496,9 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
371
496
  out := []any{}
372
497
  for _, d := range ds {
373
498
  if d.Package == e.Policy.Package && d.Subject.UID == s.UID && d.Subject.Username == s.Username {
499
+ if !e.scopeMatches(d) {
500
+ d.Revoked = true
501
+ }
374
502
  out = append(out, publicDelegation(d))
375
503
  }
376
504
  }
@@ -385,16 +513,37 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
385
513
  if code != "" {
386
514
  return deny(code)
387
515
  }
388
- if !s.Admin && (s.UID != d.Subject.UID || s.Username != d.Subject.Username) {
516
+ if (d.Lifetime != "" || !s.Admin) && (s.UID != d.Subject.UID || s.Username != d.Subject.Username) {
389
517
  return deny("DELEGATION_DENIED")
390
518
  }
391
- } else if p.Job == "" || p.Job != d.Job {
519
+ } else if d.Lifetime != "" || p.Job == "" || p.Job != d.Job {
392
520
  return deny("DELEGATION_DENIED")
393
521
  }
522
+ e.commitMu.Lock()
523
+ locked := true
524
+ defer func() {
525
+ if locked {
526
+ e.commitMu.Unlock()
527
+ }
528
+ }()
529
+ d, err = e.Store.Get(p.ID)
530
+ if err != nil {
531
+ return deny("STORAGE_ERROR")
532
+ }
394
533
  d.Revoked = true
395
534
  if e.Store.Put(d) != nil {
396
535
  return deny("STORAGE_ERROR")
397
536
  }
537
+ e.cancelGrantLocked(d.ID, "grant-revoked")
538
+ if e.auditLocked(d.Subject, "delegate.revoke", "", d.Path, "revoked") != nil {
539
+ return deny("STORAGE_ERROR")
540
+ }
541
+ pending := e.legacyPendingLocked(d.ID)
542
+ e.commitMu.Unlock()
543
+ locked = false
544
+ if waitLegacy(ctx, pending) != nil {
545
+ return deny("EXECUTOR_PAUSED")
546
+ }
398
547
  return success(map[string]any{"revoked": true})
399
548
  case "root.run":
400
549
  if !tokenRE.MatchString(p.Name) {
@@ -411,7 +560,7 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
411
560
  if !s.Admin {
412
561
  return deny("ADMIN_REQUIRED")
413
562
  }
414
- if e.DSM.Allow(ctx, s, e.Policy.App) != nil {
563
+ if e.DSM.Allow(ctx, s, e.Policy.App, e.Policy.Entry) != nil {
415
564
  return deny("APP_DENIED")
416
565
  }
417
566
  if e.Runner == nil {
@@ -424,7 +573,7 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
424
573
  return success(map[string]any{"output": out})
425
574
  }
426
575
  d, err := e.Store.Get(p.ID)
427
- if err != nil || d.Package != e.Policy.Package || d.Revoked || !e.Now().Before(d.Expires) || d.Job != p.Job || !contains(d.Ops, r.Op) || d.Scope != e.Policy.Scope {
576
+ if err != nil || d.Package != e.Policy.Package || d.Revoked || (d.Lifetime != "" || !e.Now().Before(d.Expires)) || d.Job != p.Job || !contains(d.Ops, r.Op) || !e.scopeMatches(d) {
428
577
  return deny("DELEGATION_DENIED")
429
578
  }
430
579
  s, code := e.eligible(ctx, d.Subject.Username, d.Subject.UID, true)
@@ -461,7 +610,43 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
461
610
  if !validName(p.Name) || !validRelative(p.Staging) {
462
611
  return deny("INVALID_REQUEST")
463
612
  }
464
- if err = e.Writer.Write(context.WithValue(ctx, writeJobKey{}, p.Job), s, p.Staging, real, p.Name, d.Root); err != nil {
613
+ release, err := e.acquireCopy(ctx)
614
+ if err != nil {
615
+ return deny("EXECUTOR_PAUSED")
616
+ }
617
+ defer release()
618
+ e.commitMu.Lock()
619
+ disabled, x := e.Store.Disabled()
620
+ fresh, x2 := e.Store.Get(d.ID)
621
+ e.commitMu.Unlock()
622
+ if x != nil || x2 != nil {
623
+ return deny("STORAGE_ERROR")
624
+ }
625
+ if disabled {
626
+ return deny("disabled")
627
+ }
628
+ if fresh.Revoked || ctx.Err() != nil {
629
+ return deny("DELEGATION_DENIED")
630
+ }
631
+ writerCtx := context.WithValue(ctx, writeJobKey{}, p.Job)
632
+ writerCtx = context.WithValue(writerCtx, legacyPublishKey{}, func(publish func() (MoveReceipt, error)) (MoveReceipt, error) {
633
+ current, c := e.eligible(ctx, d.Subject.Username, d.Subject.UID, true)
634
+ if c != "" || !sameIdentity(s, current) {
635
+ return MoveReceipt{}, errors.New("identity changed")
636
+ }
637
+ if _, x := e.DSM.API(ctx, s.Username, "files.checkPermission", map[string]any{"path": p.Dest}); x != nil {
638
+ return MoveReceipt{}, x
639
+ }
640
+ e.commitMu.Lock()
641
+ defer e.commitMu.Unlock()
642
+ disabled, x := e.Store.Disabled()
643
+ fresh, x2 := e.Store.Get(d.ID)
644
+ if x != nil || x2 != nil || disabled || fresh.Revoked || !e.Now().Before(fresh.Expires) || ctx.Err() != nil {
645
+ return MoveReceipt{}, errors.New("write fenced")
646
+ }
647
+ return publish()
648
+ })
649
+ if err = e.Writer.Write(writerCtx, s, p.Staging, real, p.Name, d.Root); err != nil {
465
650
  return deny("WRITE_FAILED")
466
651
  }
467
652
  return success(map[string]any{"path": strings.TrimSuffix(p.Dest, "/") + "/" + p.Name})
@@ -473,6 +658,11 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
473
658
  }
474
659
  args["name"] = p.Name
475
660
  }
661
+ done, code := e.beginLegacy(d)
662
+ if code != "" {
663
+ return deny(code)
664
+ }
665
+ defer done()
476
666
  out, err := e.DSM.API(ctx, s.Username, r.Op, args)
477
667
  if err != nil {
478
668
  return deny("DSM_FAILED")
@@ -481,7 +671,11 @@ func (e *Executor) Handle(ctx context.Context, r Request) Response {
481
671
  }
482
672
  func publicSubject(s Subject) any { return map[string]any{"username": s.Username, "uid": s.UID} }
483
673
  func publicDelegation(d Delegation) any {
484
- return map[string]any{"delegation_id": d.ID, "subject": publicSubject(d.Subject), "job_id": d.Job, "ops": d.Ops, "scope": d.Scope, "path": d.Path, "expires_at": d.Expires, "revoked": d.Revoked}
674
+ out := map[string]any{"delegation_id": d.ID, "subject": publicSubject(d.Subject), "job_id": d.Job, "ops": d.Ops, "scope": d.Scope, "path": d.Path, "expires_at": d.Expires, "revoked": d.Revoked, "destination": d.DestinationKey, "lifetime": d.Lifetime}
675
+ if d.ScopeGeneration != 0 {
676
+ out["scope_generation"] = d.ScopeGeneration
677
+ }
678
+ return out
485
679
  }
486
680
  func scopeVirtual(scope string) string {
487
681
  if scope == "home" {
@@ -522,3 +716,20 @@ func validRelative(p string) bool {
522
716
  }
523
717
  return true
524
718
  }
719
+
720
+ // Permanent destinations reject every symlink component; legacy File Station
721
+ // lookup retains its historical canonical-path semantics.
722
+ func mappedDirectory(d Delegation, p string) (string, error) {
723
+ base := d.Path
724
+ if base == "" {
725
+ base = scopeVirtual(d.Scope)
726
+ }
727
+ if base == "" || !cleanPath(d.Root) || !cleanPath(p) || !within(base, p) {
728
+ return "", errors.New("scope")
729
+ }
730
+ real := filepath.Join(d.Root, strings.TrimPrefix(strings.TrimPrefix(p, base), "/"))
731
+ if _, err := bindDirectory(real); err != nil {
732
+ return "", err
733
+ }
734
+ return real, nil
735
+ }