@synopackageland/cli 0.4.0 → 0.4.1

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 (438) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/build-spk/generator.js +3 -0
  4. package/dist/build-spk/notices.d.ts +5 -0
  5. package/dist/build-spk/notices.js +60 -0
  6. package/dist/cli.js +9 -2
  7. package/dist/error-registry.js +20 -0
  8. package/dist/init.d.ts +4 -0
  9. package/dist/init.js +20 -12
  10. package/dist/standalone/build.js +3 -1
  11. package/dist/standalone/daemon.d.ts +23 -0
  12. package/dist/standalone/daemon.js +114 -0
  13. package/dist/standalone/dsm-window.d.ts +1 -1
  14. package/dist/standalone/dsm-window.js +50 -10
  15. package/dist/standalone/helper-inventory.d.ts +16 -0
  16. package/dist/standalone/helper-inventory.js +2 -0
  17. package/dist/standalone/helper.d.ts +2 -12
  18. package/dist/standalone/helper.js +8 -7
  19. package/dist/standalone/host-adapter.d.ts +5 -0
  20. package/dist/standalone/host-adapter.js +148 -0
  21. package/dist/standalone/host-platform.d.ts +13 -0
  22. package/dist/standalone/host-platform.js +42 -0
  23. package/dist/standalone/host-types.d.ts +49 -0
  24. package/dist/standalone/host-types.js +1 -0
  25. package/dist/standalone/inspect-helper.d.ts +6 -0
  26. package/dist/standalone/inspect-helper.js +88 -0
  27. package/dist/standalone/inspect-spk.d.ts +2 -0
  28. package/dist/standalone/inspect-spk.js +49 -0
  29. package/dist/standalone/lifecycle-helper.d.ts +2 -0
  30. package/dist/standalone/lifecycle-helper.js +12 -0
  31. package/dist/standalone/lifecycle.d.ts +3 -0
  32. package/dist/standalone/lifecycle.js +53 -0
  33. package/dist/standalone/manifest.d.ts +2 -0
  34. package/dist/standalone/manifest.js +4 -1
  35. package/dist/standalone/reopen-events.d.ts +2 -0
  36. package/dist/standalone/reopen-events.js +28 -0
  37. package/dist/standalone/root-messages.js +9 -0
  38. package/dist/standalone/root-window.d.ts +4 -3
  39. package/dist/standalone/root-window.js +17 -5
  40. package/dist/standalone/spk.d.ts +4 -3
  41. package/dist/standalone/spk.js +20 -33
  42. package/dist/template-paths.d.ts +2 -0
  43. package/dist/template-paths.js +9 -0
  44. package/dist/templates/_daemon/fixture_linux_test.go +15 -0
  45. package/dist/templates/_daemon/fixture_other_test.go +7 -0
  46. package/dist/templates/_daemon/go.mod +3 -0
  47. package/dist/templates/_daemon/main.go +608 -0
  48. package/dist/templates/_daemon/main_test.go +242 -0
  49. package/dist/templates/_daemon/process_linux.go +55 -0
  50. package/dist/templates/_daemon/process_linux_test.go +161 -0
  51. package/dist/templates/_daemon/process_other.go +14 -0
  52. package/dist/templates/_daemon/testdata/child.go +41 -0
  53. package/dist/templates/_shared/GO-LICENSE +27 -0
  54. package/dist/templates/_shared/GO-PATENTS +22 -0
  55. package/dist/templates/_shared/LICENSES/TEMPLATE.txt +21 -0
  56. package/dist/templates/_shared/notice-overrides/index.json +14 -0
  57. package/dist/templates/_shared/notice-overrides/utils-0.4.2-LICENSE +21 -0
  58. package/dist/templates/_shared/notice-overrides/utils-0.5.0-LICENSE +21 -0
  59. package/dist/templates/_shared/third-party-notices.mjs +51 -0
  60. package/dist/templates/native-go/README.md +57 -1
  61. package/dist/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +59 -0
  62. package/dist/templates/native-go/backend/cmd/dsm-readonly-probe/main_linux.go +46 -0
  63. package/dist/templates/native-go/backend/go.mod +4 -0
  64. package/dist/templates/native-go/backend/go.sum +4 -0
  65. package/dist/templates/native-go/backend/internal/api/binary.go +154 -0
  66. package/dist/templates/native-go/backend/internal/api/binary_test.go +165 -0
  67. package/dist/templates/native-go/backend/internal/api/registry.go +14 -4
  68. package/dist/templates/native-go/backend/internal/api/server.go +4 -0
  69. package/dist/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +121 -0
  70. package/dist/templates/native-go/backend/internal/app/nas_refusal_test.go +46 -0
  71. package/dist/templates/native-go/backend/internal/app/routes.go +11 -0
  72. package/dist/templates/native-go/backend/internal/app/routes_test.go +10 -5
  73. package/dist/templates/native-go/backend/internal/dsmprobe/collector_linux.go +335 -0
  74. package/dist/templates/native-go/backend/internal/dsmprobe/collector_linux_test.go +41 -0
  75. package/dist/templates/native-go/backend/internal/dsmprobe/protocol.go +179 -0
  76. package/dist/templates/native-go/backend/internal/dsmprobe/protocol_test.go +29 -0
  77. package/dist/templates/native-go/backend/internal/helper/cohort.go +393 -0
  78. package/dist/templates/native-go/backend/internal/helper/cohort_publication.go +18 -0
  79. package/dist/templates/native-go/backend/internal/helper/cohort_publication_test.go +105 -0
  80. package/dist/templates/native-go/backend/internal/helper/cohort_test.go +372 -0
  81. package/dist/templates/native-go/backend/internal/helper/control.go +226 -21
  82. package/dist/templates/native-go/backend/internal/helper/control_test.go +10 -2
  83. package/dist/templates/native-go/backend/internal/helper/data_contract.go +242 -0
  84. package/dist/templates/native-go/backend/internal/helper/data_contract_test.go +51 -0
  85. package/dist/templates/native-go/backend/internal/helper/data_decode.go +184 -0
  86. package/dist/templates/native-go/backend/internal/helper/data_decode_pointer_test.go +30 -0
  87. package/dist/templates/native-go/backend/internal/helper/data_protocol.go +416 -0
  88. package/dist/templates/native-go/backend/internal/helper/data_protocol_test.go +449 -0
  89. package/dist/templates/native-go/backend/internal/helper/dsm_acl_linux.go +139 -0
  90. package/dist/templates/native-go/backend/internal/helper/dsm_acl_linux_test.go +121 -0
  91. package/dist/templates/native-go/backend/internal/helper/dsm_acl_syscall_linux.go +95 -0
  92. package/dist/templates/native-go/backend/internal/helper/dsm_acl_syscall_linux_test.go +56 -0
  93. package/dist/templates/native-go/backend/internal/helper/dsm_bridge_linux.go +144 -0
  94. package/dist/templates/native-go/backend/internal/helper/dsm_bridge_linux_test.go +89 -0
  95. package/dist/templates/native-go/backend/internal/helper/dsm_home_linux.go +107 -0
  96. package/dist/templates/native-go/backend/internal/helper/dsm_home_linux_test.go +82 -0
  97. package/dist/templates/native-go/backend/internal/helper/dsm_probe_transport_linux.go +151 -0
  98. package/dist/templates/native-go/backend/internal/helper/dsm_probe_transport_linux_test.go +66 -0
  99. package/dist/templates/native-go/backend/internal/helper/grant_authority.go +516 -0
  100. package/dist/templates/native-go/backend/internal/helper/grant_authority_contract.go +88 -0
  101. package/dist/templates/native-go/backend/internal/helper/grant_authority_test.go +510 -0
  102. package/dist/templates/native-go/backend/internal/helper/grant_cohort.go +389 -0
  103. package/dist/templates/native-go/backend/internal/helper/grant_cohort_test.go +239 -0
  104. package/dist/templates/native-go/backend/internal/helper/grant_delivery.go +49 -0
  105. package/dist/templates/native-go/backend/internal/helper/grant_delivery_methods.go +273 -0
  106. package/dist/templates/native-go/backend/internal/helper/grant_delivery_test.go +322 -0
  107. package/dist/templates/native-go/backend/internal/helper/grant_operation_contract.go +47 -0
  108. package/dist/templates/native-go/backend/internal/helper/grant_readonly_test.go +180 -0
  109. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux.go +415 -0
  110. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +468 -0
  111. package/dist/templates/native-go/backend/internal/helper/grant_rpc.go +147 -0
  112. package/dist/templates/native-go/backend/internal/helper/grant_rpc_test.go +51 -0
  113. package/dist/templates/native-go/backend/internal/helper/grant_snapshot.go +75 -0
  114. package/dist/templates/native-go/backend/internal/helper/grant_supervisor_linux.go +134 -0
  115. package/dist/templates/native-go/backend/internal/helper/grant_supervisor_linux_test.go +254 -0
  116. package/dist/templates/native-go/backend/internal/helper/lease.go +112 -0
  117. package/dist/templates/native-go/backend/internal/helper/lease_linux.go +44 -0
  118. package/dist/templates/native-go/backend/internal/helper/lease_other.go +8 -0
  119. package/dist/templates/native-go/backend/internal/helper/lifecycle.go +234 -0
  120. package/dist/templates/native-go/backend/internal/helper/lifecycle_test.go +473 -0
  121. package/dist/templates/native-go/backend/internal/helper/managed_pilot.go +284 -0
  122. package/dist/templates/native-go/backend/internal/helper/managed_pilot_repository_linux.go +74 -0
  123. package/dist/templates/native-go/backend/internal/helper/managed_pilot_repository_linux_test.go +40 -0
  124. package/dist/templates/native-go/backend/internal/helper/managed_pilot_test.go +210 -0
  125. package/dist/templates/native-go/backend/internal/helper/nas_shipped_refusal_linux_test.go +97 -0
  126. package/dist/templates/native-go/backend/internal/helper/native_discovery.go +79 -0
  127. package/dist/templates/native-go/backend/internal/helper/native_identity.go +89 -0
  128. package/dist/templates/native-go/backend/internal/helper/native_provider.go +159 -0
  129. package/dist/templates/native-go/backend/internal/helper/native_provider_test.go +421 -0
  130. package/dist/templates/native-go/backend/internal/helper/observation.go +204 -0
  131. package/dist/templates/native-go/backend/internal/helper/observation_health_test.go +115 -0
  132. package/dist/templates/native-go/backend/internal/helper/observation_reload_budget_test.go +117 -0
  133. package/dist/templates/native-go/backend/internal/helper/observation_test.go +196 -0
  134. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic.go +109 -0
  135. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_linux.go +76 -0
  136. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +124 -0
  137. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_test.go +98 -0
  138. package/dist/templates/native-go/backend/internal/helper/operation_bootstrap_linux.go +193 -0
  139. package/dist/templates/native-go/backend/internal/helper/operation_credentials_linux.go +56 -0
  140. package/dist/templates/native-go/backend/internal/helper/operation_linux.go +480 -0
  141. package/dist/templates/native-go/backend/internal/helper/operation_linux_test.go +528 -0
  142. package/dist/templates/native-go/backend/internal/helper/process_linux.go +11 -1
  143. package/dist/templates/native-go/backend/internal/helper/publication_test.go +178 -0
  144. package/dist/templates/native-go/backend/internal/helper/read_stream_cohort_linux_test.go +156 -0
  145. package/dist/templates/native-go/backend/internal/helper/read_transfer.go +40 -0
  146. package/dist/templates/native-go/backend/internal/helper/read_transfer_linux.go +109 -0
  147. package/dist/templates/native-go/backend/internal/helper/read_transfer_linux_test.go +118 -0
  148. package/dist/templates/native-go/backend/internal/helper/read_transfer_test.go +177 -0
  149. package/dist/templates/native-go/backend/internal/helper/readonly.go +105 -0
  150. package/dist/templates/native-go/backend/internal/helper/readonly_admission.go +85 -0
  151. package/dist/templates/native-go/backend/internal/helper/readonly_cohort_linux_test.go +253 -0
  152. package/dist/templates/native-go/backend/internal/helper/readonly_linux_test.go +378 -0
  153. package/dist/templates/native-go/backend/internal/helper/readonly_mount_linux.go +65 -0
  154. package/dist/templates/native-go/backend/internal/helper/readonly_mount_proxy_linux.go +113 -0
  155. package/dist/templates/native-go/backend/internal/helper/readonly_operations_linux.go +168 -0
  156. package/dist/templates/native-go/backend/internal/helper/readonly_resolver_linux.go +241 -0
  157. package/dist/templates/native-go/backend/internal/helper/readonly_test.go +40 -0
  158. package/dist/templates/native-go/backend/internal/helper/relaxed_identity.go +252 -0
  159. package/dist/templates/native-go/backend/internal/helper/relaxed_identity_contract.go +105 -0
  160. package/dist/templates/native-go/backend/internal/helper/relaxed_identity_test.go +406 -0
  161. package/dist/templates/native-go/backend/internal/helper/relaxed_invalidation.go +200 -0
  162. package/dist/templates/native-go/backend/internal/helper/repair.go +132 -0
  163. package/dist/templates/native-go/backend/internal/helper/repair_test.go +271 -0
  164. package/dist/templates/native-go/backend/internal/helper/s1r_actor_acl_linux_test.go +303 -0
  165. package/dist/templates/native-go/backend/internal/helper/s1r_actor_fixture_linux_test.go +184 -0
  166. package/dist/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +231 -0
  167. package/dist/templates/native-go/backend/internal/helper/s1r_fixture_contract_linux_test.go +18 -0
  168. package/dist/templates/native-go/backend/internal/helper/server_linux.go +44 -3
  169. package/dist/templates/native-go/backend/internal/helper/status.go +74 -11
  170. package/dist/templates/native-go/backend/internal/helper/status_test.go +83 -0
  171. package/dist/templates/native-go/backend/internal/helper/supervision.go +28 -5
  172. package/dist/templates/native-go/backend/internal/helper/supervisor_frame.go +76 -0
  173. package/dist/templates/native-go/backend/internal/helper/supervisor_frame_linux_test.go +69 -0
  174. package/dist/templates/native-go/backend/internal/helper/supervisor_frame_test.go +107 -0
  175. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +187 -16
  176. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +618 -7
  177. package/dist/templates/native-go/backend/internal/helper/supervisor_socket_eof_test.go +74 -0
  178. package/dist/templates/native-go/backend/internal/helper/supervisor_socket_test.go +45 -0
  179. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/create.json +23 -0
  180. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/frames.json +67 -0
  181. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/grant-store.json +52 -0
  182. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/intent.json +20 -0
  183. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/list.json +20 -0
  184. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/publish.json +20 -0
  185. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/read.json +22 -0
  186. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/stat.json +22 -0
  187. package/dist/templates/native-go/backend/internal/helper/testdata/protocol2/write-stream.json +22 -0
  188. package/dist/templates/native-go/backend/internal/helper/unit_connection.go +75 -0
  189. package/dist/templates/native-go/backend/internal/helper/unit_connection_linux_test.go +135 -0
  190. package/dist/templates/native-go/backend/internal/helper/unit_connection_test.go +133 -0
  191. package/dist/templates/native-go/backend/internal/helper/unit_frame_test.go +32 -0
  192. package/dist/templates/native-go/backend/internal/helper/unit_linux.go +364 -0
  193. package/dist/templates/native-go/backend/internal/helper/unit_linux_test.go +46 -0
  194. package/dist/templates/native-go/backend/internal/helper/unit_mechanisms_linux_test.go +217 -0
  195. package/dist/templates/native-go/backend/internal/helper/unit_padding_linux_test.go +178 -0
  196. package/dist/templates/native-go/backend/internal/helper/unit_query_linux.go +111 -0
  197. package/dist/templates/native-go/backend/internal/helper/unit_reload_linux_test.go +372 -0
  198. package/dist/templates/native-go/backend/internal/helper/unit_reload_sequence_linux_test.go +232 -0
  199. package/dist/templates/native-go/backend/internal/helper/write.go +77 -0
  200. package/dist/templates/native-go/backend/internal/helper/write_admission.go +260 -0
  201. package/dist/templates/native-go/backend/internal/helper/write_admission_test.go +135 -0
  202. package/dist/templates/native-go/backend/internal/helper/write_cohort_linux_test.go +218 -0
  203. package/dist/templates/native-go/backend/internal/helper/write_files_linux.go +202 -0
  204. package/dist/templates/native-go/backend/internal/helper/write_files_linux_test.go +199 -0
  205. package/dist/templates/native-go/backend/internal/helper/write_intent_linux.go +116 -0
  206. package/dist/templates/native-go/backend/internal/helper/write_intent_linux_test.go +74 -0
  207. package/dist/templates/native-go/backend/internal/helper/write_operation_linux.go +245 -0
  208. package/dist/templates/native-go/backend/internal/helper/write_test.go +53 -0
  209. package/dist/templates/native-go/ui/app.js +8 -1
  210. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  211. package/dist/templates/native-go-vue/README.md +115 -1
  212. package/dist/templates/native-go-vue/frontend/DESIGN.md +1 -1
  213. package/dist/templates/native-go-vue/frontend/package-lock.json +132 -229
  214. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  215. package/dist/templates/native-go-vue/frontend/src/App.vue +33 -6
  216. package/dist/templates/native-go-vue/frontend/src/context.ts +41 -2
  217. package/dist/templates/native-go-vue/frontend/src/main.ts +4 -1
  218. package/dist/templates/native-go-vue/frontend/src/nas/NasGrantSettings.vue +74 -0
  219. package/dist/templates/native-go-vue/frontend/src/nas/NasPathPicker.vue +106 -0
  220. package/dist/templates/native-go-vue/frontend/src/nas/NasUnavailable.vue +20 -0
  221. package/dist/templates/native-go-vue/frontend/src/nas/i18n/de.json +74 -0
  222. package/dist/templates/native-go-vue/frontend/src/nas/i18n/en.json +74 -0
  223. package/dist/templates/native-go-vue/frontend/src/nas/i18n/es.json +74 -0
  224. package/dist/templates/native-go-vue/frontend/src/nas/i18n/fr.json +74 -0
  225. package/dist/templates/native-go-vue/frontend/src/nas/i18n/it.json +74 -0
  226. package/dist/templates/native-go-vue/frontend/src/nas/i18n/ja.json +74 -0
  227. package/dist/templates/native-go-vue/frontend/src/nas/i18n/ko.json +74 -0
  228. package/dist/templates/native-go-vue/frontend/src/nas/i18n/zh-CN.json +74 -0
  229. package/dist/templates/native-go-vue/frontend/src/nas/i18n/zh-TW.json +74 -0
  230. package/dist/templates/native-go-vue/frontend/src/nas/i18n.ts +38 -0
  231. package/dist/templates/native-go-vue/frontend/src/nas/mock-provider.ts +119 -0
  232. package/dist/templates/native-go-vue/frontend/src/nas/provider.ts +59 -0
  233. package/dist/templates/native-go-vue/frontend/src/nas/use-request.ts +27 -0
  234. package/dist/templates/native-go-vue/frontend/src/style.css +17 -0
  235. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +44 -2
  236. package/dist/templates/native-go-vue/frontend/test/nas-provider.test.ts +81 -0
  237. package/dist/templates/native-go-vue/frontend/test/nas-request.test.ts +21 -0
  238. package/dist/templates/native-go-vue/frontend/test/nas-ui.test.ts +261 -0
  239. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -1
  240. package/dist/templates/native-go-vue/frontend/vite.config.ts +6 -4
  241. package/package.json +3 -2
  242. package/templates/_daemon/fixture_linux_test.go +15 -0
  243. package/templates/_daemon/fixture_other_test.go +7 -0
  244. package/templates/_daemon/go.mod +3 -0
  245. package/templates/_daemon/main.go +608 -0
  246. package/templates/_daemon/main_test.go +242 -0
  247. package/templates/_daemon/process_linux.go +55 -0
  248. package/templates/_daemon/process_linux_test.go +161 -0
  249. package/templates/_daemon/process_other.go +14 -0
  250. package/templates/_daemon/testdata/child.go +41 -0
  251. package/templates/_shared/GO-LICENSE +27 -0
  252. package/templates/_shared/GO-PATENTS +22 -0
  253. package/templates/_shared/LICENSES/TEMPLATE.txt +21 -0
  254. package/templates/_shared/notice-overrides/index.json +14 -0
  255. package/templates/_shared/notice-overrides/utils-0.4.2-LICENSE +21 -0
  256. package/templates/_shared/notice-overrides/utils-0.5.0-LICENSE +21 -0
  257. package/templates/_shared/third-party-notices.mjs +51 -0
  258. package/templates/native-go/README.md +57 -1
  259. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +59 -0
  260. package/templates/native-go/backend/cmd/dsm-readonly-probe/main_linux.go +46 -0
  261. package/templates/native-go/backend/go.mod +4 -0
  262. package/templates/native-go/backend/go.sum +4 -0
  263. package/templates/native-go/backend/internal/api/binary.go +154 -0
  264. package/templates/native-go/backend/internal/api/binary_test.go +165 -0
  265. package/templates/native-go/backend/internal/api/registry.go +14 -4
  266. package/templates/native-go/backend/internal/api/server.go +4 -0
  267. package/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +121 -0
  268. package/templates/native-go/backend/internal/app/nas_refusal_test.go +46 -0
  269. package/templates/native-go/backend/internal/app/routes.go +11 -0
  270. package/templates/native-go/backend/internal/app/routes_test.go +10 -5
  271. package/templates/native-go/backend/internal/dsmprobe/collector_linux.go +335 -0
  272. package/templates/native-go/backend/internal/dsmprobe/collector_linux_test.go +41 -0
  273. package/templates/native-go/backend/internal/dsmprobe/protocol.go +179 -0
  274. package/templates/native-go/backend/internal/dsmprobe/protocol_test.go +29 -0
  275. package/templates/native-go/backend/internal/helper/cohort.go +393 -0
  276. package/templates/native-go/backend/internal/helper/cohort_publication.go +18 -0
  277. package/templates/native-go/backend/internal/helper/cohort_publication_test.go +105 -0
  278. package/templates/native-go/backend/internal/helper/cohort_test.go +372 -0
  279. package/templates/native-go/backend/internal/helper/control.go +226 -21
  280. package/templates/native-go/backend/internal/helper/control_test.go +10 -2
  281. package/templates/native-go/backend/internal/helper/data_contract.go +242 -0
  282. package/templates/native-go/backend/internal/helper/data_contract_test.go +51 -0
  283. package/templates/native-go/backend/internal/helper/data_decode.go +184 -0
  284. package/templates/native-go/backend/internal/helper/data_decode_pointer_test.go +30 -0
  285. package/templates/native-go/backend/internal/helper/data_protocol.go +416 -0
  286. package/templates/native-go/backend/internal/helper/data_protocol_test.go +449 -0
  287. package/templates/native-go/backend/internal/helper/dsm_acl_linux.go +139 -0
  288. package/templates/native-go/backend/internal/helper/dsm_acl_linux_test.go +121 -0
  289. package/templates/native-go/backend/internal/helper/dsm_acl_syscall_linux.go +95 -0
  290. package/templates/native-go/backend/internal/helper/dsm_acl_syscall_linux_test.go +56 -0
  291. package/templates/native-go/backend/internal/helper/dsm_bridge_linux.go +144 -0
  292. package/templates/native-go/backend/internal/helper/dsm_bridge_linux_test.go +89 -0
  293. package/templates/native-go/backend/internal/helper/dsm_home_linux.go +107 -0
  294. package/templates/native-go/backend/internal/helper/dsm_home_linux_test.go +82 -0
  295. package/templates/native-go/backend/internal/helper/dsm_probe_transport_linux.go +151 -0
  296. package/templates/native-go/backend/internal/helper/dsm_probe_transport_linux_test.go +66 -0
  297. package/templates/native-go/backend/internal/helper/grant_authority.go +516 -0
  298. package/templates/native-go/backend/internal/helper/grant_authority_contract.go +88 -0
  299. package/templates/native-go/backend/internal/helper/grant_authority_test.go +510 -0
  300. package/templates/native-go/backend/internal/helper/grant_cohort.go +389 -0
  301. package/templates/native-go/backend/internal/helper/grant_cohort_test.go +239 -0
  302. package/templates/native-go/backend/internal/helper/grant_delivery.go +49 -0
  303. package/templates/native-go/backend/internal/helper/grant_delivery_methods.go +273 -0
  304. package/templates/native-go/backend/internal/helper/grant_delivery_test.go +322 -0
  305. package/templates/native-go/backend/internal/helper/grant_operation_contract.go +47 -0
  306. package/templates/native-go/backend/internal/helper/grant_readonly_test.go +180 -0
  307. package/templates/native-go/backend/internal/helper/grant_repository_linux.go +415 -0
  308. package/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +468 -0
  309. package/templates/native-go/backend/internal/helper/grant_rpc.go +147 -0
  310. package/templates/native-go/backend/internal/helper/grant_rpc_test.go +51 -0
  311. package/templates/native-go/backend/internal/helper/grant_snapshot.go +75 -0
  312. package/templates/native-go/backend/internal/helper/grant_supervisor_linux.go +134 -0
  313. package/templates/native-go/backend/internal/helper/grant_supervisor_linux_test.go +254 -0
  314. package/templates/native-go/backend/internal/helper/lease.go +112 -0
  315. package/templates/native-go/backend/internal/helper/lease_linux.go +44 -0
  316. package/templates/native-go/backend/internal/helper/lease_other.go +8 -0
  317. package/templates/native-go/backend/internal/helper/lifecycle.go +234 -0
  318. package/templates/native-go/backend/internal/helper/lifecycle_test.go +473 -0
  319. package/templates/native-go/backend/internal/helper/managed_pilot.go +284 -0
  320. package/templates/native-go/backend/internal/helper/managed_pilot_repository_linux.go +74 -0
  321. package/templates/native-go/backend/internal/helper/managed_pilot_repository_linux_test.go +40 -0
  322. package/templates/native-go/backend/internal/helper/managed_pilot_test.go +210 -0
  323. package/templates/native-go/backend/internal/helper/nas_shipped_refusal_linux_test.go +97 -0
  324. package/templates/native-go/backend/internal/helper/native_discovery.go +79 -0
  325. package/templates/native-go/backend/internal/helper/native_identity.go +89 -0
  326. package/templates/native-go/backend/internal/helper/native_provider.go +159 -0
  327. package/templates/native-go/backend/internal/helper/native_provider_test.go +421 -0
  328. package/templates/native-go/backend/internal/helper/observation.go +204 -0
  329. package/templates/native-go/backend/internal/helper/observation_health_test.go +115 -0
  330. package/templates/native-go/backend/internal/helper/observation_reload_budget_test.go +117 -0
  331. package/templates/native-go/backend/internal/helper/observation_test.go +196 -0
  332. package/templates/native-go/backend/internal/helper/observer_diagnostic.go +109 -0
  333. package/templates/native-go/backend/internal/helper/observer_diagnostic_linux.go +76 -0
  334. package/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +124 -0
  335. package/templates/native-go/backend/internal/helper/observer_diagnostic_test.go +98 -0
  336. package/templates/native-go/backend/internal/helper/operation_bootstrap_linux.go +193 -0
  337. package/templates/native-go/backend/internal/helper/operation_credentials_linux.go +56 -0
  338. package/templates/native-go/backend/internal/helper/operation_linux.go +480 -0
  339. package/templates/native-go/backend/internal/helper/operation_linux_test.go +528 -0
  340. package/templates/native-go/backend/internal/helper/process_linux.go +11 -1
  341. package/templates/native-go/backend/internal/helper/publication_test.go +178 -0
  342. package/templates/native-go/backend/internal/helper/read_stream_cohort_linux_test.go +156 -0
  343. package/templates/native-go/backend/internal/helper/read_transfer.go +40 -0
  344. package/templates/native-go/backend/internal/helper/read_transfer_linux.go +109 -0
  345. package/templates/native-go/backend/internal/helper/read_transfer_linux_test.go +118 -0
  346. package/templates/native-go/backend/internal/helper/read_transfer_test.go +177 -0
  347. package/templates/native-go/backend/internal/helper/readonly.go +105 -0
  348. package/templates/native-go/backend/internal/helper/readonly_admission.go +85 -0
  349. package/templates/native-go/backend/internal/helper/readonly_cohort_linux_test.go +253 -0
  350. package/templates/native-go/backend/internal/helper/readonly_linux_test.go +378 -0
  351. package/templates/native-go/backend/internal/helper/readonly_mount_linux.go +65 -0
  352. package/templates/native-go/backend/internal/helper/readonly_mount_proxy_linux.go +113 -0
  353. package/templates/native-go/backend/internal/helper/readonly_operations_linux.go +168 -0
  354. package/templates/native-go/backend/internal/helper/readonly_resolver_linux.go +241 -0
  355. package/templates/native-go/backend/internal/helper/readonly_test.go +40 -0
  356. package/templates/native-go/backend/internal/helper/relaxed_identity.go +252 -0
  357. package/templates/native-go/backend/internal/helper/relaxed_identity_contract.go +105 -0
  358. package/templates/native-go/backend/internal/helper/relaxed_identity_test.go +406 -0
  359. package/templates/native-go/backend/internal/helper/relaxed_invalidation.go +200 -0
  360. package/templates/native-go/backend/internal/helper/repair.go +132 -0
  361. package/templates/native-go/backend/internal/helper/repair_test.go +271 -0
  362. package/templates/native-go/backend/internal/helper/s1r_actor_acl_linux_test.go +303 -0
  363. package/templates/native-go/backend/internal/helper/s1r_actor_fixture_linux_test.go +184 -0
  364. package/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +231 -0
  365. package/templates/native-go/backend/internal/helper/s1r_fixture_contract_linux_test.go +18 -0
  366. package/templates/native-go/backend/internal/helper/server_linux.go +44 -3
  367. package/templates/native-go/backend/internal/helper/status.go +74 -11
  368. package/templates/native-go/backend/internal/helper/status_test.go +83 -0
  369. package/templates/native-go/backend/internal/helper/supervision.go +28 -5
  370. package/templates/native-go/backend/internal/helper/supervisor_frame.go +76 -0
  371. package/templates/native-go/backend/internal/helper/supervisor_frame_linux_test.go +69 -0
  372. package/templates/native-go/backend/internal/helper/supervisor_frame_test.go +107 -0
  373. package/templates/native-go/backend/internal/helper/supervisor_linux.go +187 -16
  374. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +618 -7
  375. package/templates/native-go/backend/internal/helper/supervisor_socket_eof_test.go +74 -0
  376. package/templates/native-go/backend/internal/helper/supervisor_socket_test.go +45 -0
  377. package/templates/native-go/backend/internal/helper/testdata/protocol2/create.json +23 -0
  378. package/templates/native-go/backend/internal/helper/testdata/protocol2/frames.json +67 -0
  379. package/templates/native-go/backend/internal/helper/testdata/protocol2/grant-store.json +52 -0
  380. package/templates/native-go/backend/internal/helper/testdata/protocol2/intent.json +20 -0
  381. package/templates/native-go/backend/internal/helper/testdata/protocol2/list.json +20 -0
  382. package/templates/native-go/backend/internal/helper/testdata/protocol2/publish.json +20 -0
  383. package/templates/native-go/backend/internal/helper/testdata/protocol2/read.json +22 -0
  384. package/templates/native-go/backend/internal/helper/testdata/protocol2/stat.json +22 -0
  385. package/templates/native-go/backend/internal/helper/testdata/protocol2/write-stream.json +22 -0
  386. package/templates/native-go/backend/internal/helper/unit_connection.go +75 -0
  387. package/templates/native-go/backend/internal/helper/unit_connection_linux_test.go +135 -0
  388. package/templates/native-go/backend/internal/helper/unit_connection_test.go +133 -0
  389. package/templates/native-go/backend/internal/helper/unit_frame_test.go +32 -0
  390. package/templates/native-go/backend/internal/helper/unit_linux.go +364 -0
  391. package/templates/native-go/backend/internal/helper/unit_linux_test.go +46 -0
  392. package/templates/native-go/backend/internal/helper/unit_mechanisms_linux_test.go +217 -0
  393. package/templates/native-go/backend/internal/helper/unit_padding_linux_test.go +178 -0
  394. package/templates/native-go/backend/internal/helper/unit_query_linux.go +111 -0
  395. package/templates/native-go/backend/internal/helper/unit_reload_linux_test.go +372 -0
  396. package/templates/native-go/backend/internal/helper/unit_reload_sequence_linux_test.go +232 -0
  397. package/templates/native-go/backend/internal/helper/write.go +77 -0
  398. package/templates/native-go/backend/internal/helper/write_admission.go +260 -0
  399. package/templates/native-go/backend/internal/helper/write_admission_test.go +135 -0
  400. package/templates/native-go/backend/internal/helper/write_cohort_linux_test.go +218 -0
  401. package/templates/native-go/backend/internal/helper/write_files_linux.go +202 -0
  402. package/templates/native-go/backend/internal/helper/write_files_linux_test.go +199 -0
  403. package/templates/native-go/backend/internal/helper/write_intent_linux.go +116 -0
  404. package/templates/native-go/backend/internal/helper/write_intent_linux_test.go +74 -0
  405. package/templates/native-go/backend/internal/helper/write_operation_linux.go +245 -0
  406. package/templates/native-go/backend/internal/helper/write_test.go +53 -0
  407. package/templates/native-go/ui/app.js +8 -1
  408. package/templates/native-go-better-auth/node/build.mjs +2 -0
  409. package/templates/native-go-vue/README.md +115 -1
  410. package/templates/native-go-vue/frontend/DESIGN.md +1 -1
  411. package/templates/native-go-vue/frontend/package-lock.json +132 -229
  412. package/templates/native-go-vue/frontend/package.json +1 -1
  413. package/templates/native-go-vue/frontend/src/App.vue +33 -6
  414. package/templates/native-go-vue/frontend/src/context.ts +41 -2
  415. package/templates/native-go-vue/frontend/src/main.ts +4 -1
  416. package/templates/native-go-vue/frontend/src/nas/NasGrantSettings.vue +74 -0
  417. package/templates/native-go-vue/frontend/src/nas/NasPathPicker.vue +106 -0
  418. package/templates/native-go-vue/frontend/src/nas/NasUnavailable.vue +20 -0
  419. package/templates/native-go-vue/frontend/src/nas/i18n/de.json +74 -0
  420. package/templates/native-go-vue/frontend/src/nas/i18n/en.json +74 -0
  421. package/templates/native-go-vue/frontend/src/nas/i18n/es.json +74 -0
  422. package/templates/native-go-vue/frontend/src/nas/i18n/fr.json +74 -0
  423. package/templates/native-go-vue/frontend/src/nas/i18n/it.json +74 -0
  424. package/templates/native-go-vue/frontend/src/nas/i18n/ja.json +74 -0
  425. package/templates/native-go-vue/frontend/src/nas/i18n/ko.json +74 -0
  426. package/templates/native-go-vue/frontend/src/nas/i18n/zh-CN.json +74 -0
  427. package/templates/native-go-vue/frontend/src/nas/i18n/zh-TW.json +74 -0
  428. package/templates/native-go-vue/frontend/src/nas/i18n.ts +38 -0
  429. package/templates/native-go-vue/frontend/src/nas/mock-provider.ts +119 -0
  430. package/templates/native-go-vue/frontend/src/nas/provider.ts +59 -0
  431. package/templates/native-go-vue/frontend/src/nas/use-request.ts +27 -0
  432. package/templates/native-go-vue/frontend/src/style.css +17 -0
  433. package/templates/native-go-vue/frontend/test/apps.test.ts +44 -2
  434. package/templates/native-go-vue/frontend/test/nas-provider.test.ts +81 -0
  435. package/templates/native-go-vue/frontend/test/nas-request.test.ts +21 -0
  436. package/templates/native-go-vue/frontend/test/nas-ui.test.ts +261 -0
  437. package/templates/native-go-vue/frontend/tsconfig.json +1 -1
  438. package/templates/native-go-vue/frontend/vite.config.ts +6 -4
@@ -1,18 +1,19 @@
1
- import { createHash } from "node:crypto";
2
1
  import { lstatSync, readFileSync, realpathSync } from "node:fs";
3
2
  import { isAbsolute, join, relative, resolve } from "node:path";
4
3
  import { renderRootTaskScript } from "./root-ops.js";
5
4
  import { StandaloneBuildError } from "./spk.js";
6
- const sha256 = (b) => createHash("sha256").update(b).digest("hex");
5
+ import { sha256 } from "./helper-inventory.js";
7
6
  const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
8
7
  export function readIntegration(raw, root, fail) {
9
8
  if (raw === undefined)
10
9
  return undefined;
11
- const invalid = () => { fail({ code: "STANDALONE_INTEGRATION_INVALID", message: "integration 需要 protocol: 1 與 workers: {固定 ID: 專案內檔案};最多 16 個,僅作 inventory,不執行 worker。" }); return undefined; };
12
- if (!record(raw) || Object.keys(raw).some(k => !["protocol", "workers", "identity"].includes(k)) || raw.protocol !== 1 || !record(raw.workers) || Object.keys(raw.workers).length > 16)
10
+ const invalid = () => { fail({ code: "STANDALONE_INTEGRATION_INVALID", message: "integration 需要 protocol: 1 或 2 與 workers: {固定 ID: 專案內檔案};最多 16 個,僅作 inventory,不執行 worker。" }); return undefined; };
11
+ if (!record(raw) || Object.keys(raw).some(k => !["protocol", "workers", "identity", "dataAccess"].includes(k)) || ![1, 2].includes(raw.protocol) || !record(raw.workers) || Object.keys(raw.workers).length > 16)
13
12
  return invalid();
14
13
  if (raw.identity !== undefined && raw.identity !== "better-auth")
15
14
  return invalid();
15
+ if (raw.dataAccess !== undefined && (raw.protocol !== 2 || !["unavailable", "relaxed"].includes(raw.dataAccess)))
16
+ return invalid();
16
17
  const workers = {};
17
18
  for (const [id, file] of Object.entries(raw.workers)) {
18
19
  if (!/^[a-z][a-z0-9_]{0,31}$/.test(id) || typeof file !== "string" || !file || isAbsolute(file) || file.split(/[\\/]/).includes(".."))
@@ -22,7 +23,7 @@ export function readIntegration(raw, root, fail) {
22
23
  return invalid();
23
24
  workers[id] = path;
24
25
  }
25
- return { protocol: 1, workers, ...(raw.identity === "better-auth" ? { identity: "better-auth" } : {}) };
26
+ return { protocol: raw.protocol, workers, ...(raw.identity === "better-auth" ? { identity: "better-auth" } : {}), ...(raw.dataAccess !== undefined ? { dataAccess: raw.dataAccess } : {}) };
26
27
  }
27
28
  function snapshotWorkers(m) {
28
29
  let total = 0;
@@ -44,7 +45,7 @@ export function assertHelperTaskBudget(script, metadata = JSON.stringify({ scrip
44
45
  throw new StandaloneBuildError("HELPER_METADATA_TOO_LARGE", "helper scheduler metadata 超過 8191 bytes 有界限制。");
45
46
  }
46
47
  export function helperOps(m, inventory) {
47
- return ["enable", "recover", "teardown", "disable"].map(action => {
48
+ return ["enable", "recover", "teardown", "disable", "repair"].map(action => {
48
49
  const op = { name: `helper_${action}`, protectedOutput: true, body: `#!/bin/sh
49
50
  set -eu
50
51
  umask 077
@@ -92,7 +93,7 @@ export function buildHelper(m, arch, work, run) {
92
93
  throw new StandaloneBuildError("HELPER_GO_REQUIRED", "M1a integration 僅支援 native-go。");
93
94
  const workers = snapshotWorkers(m);
94
95
  const registry = workers.map(({ id, digest }) => ({ id, digest }));
95
- const base = { package: m.package, version: m.spkVersion, protocol: m.integration.protocol, payloadDigest: "", registryDigest: sha256(JSON.stringify(registry)) };
96
+ const base = { package: m.package, version: m.spkVersion, protocol: m.integration.protocol, payloadDigest: "", registryDigest: sha256(JSON.stringify(registry)), ...(m.integration.dataAccess ? { dataAccess: m.integration.dataAccess } : {}) };
96
97
  const binary = join(work, `helper-${arch}`);
97
98
  const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", `-s -w -buildid= -X 'main.helperInventory=${JSON.stringify(base)}'`, "-o", binary, "./cmd/helper"], {
98
99
  cwd: m.backend.dir, env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: arch === "x86_64" ? "amd64" : "arm64" },
@@ -0,0 +1,5 @@
1
+ export type { HostAdapter, HostTitleResult, HostRequestResult, HostURLResult, HostEvent, HostSubscriptionResult, HostFileNavigationResult, HostUnsupportedResult } from "./host-types.js";
2
+ export declare const HOST_ADAPTER_RUNTIME = "function inspectHostPlatform(ctx) {\n ctx = ctx || {};\n var web = typeof window !== \"undefined\";\n var host = false;\n try {\n host = !!(web && ctx.appWindow && typeof SYNO !== \"undefined\" &&\n SYNO.SDS && typeof SYNO.SDS.AppWindow === \"function\" &&\n ctx.appWindow instanceof SYNO.SDS.AppWindow);\n } catch (error) {\n // An unsupported host constructor must not break a standalone shell.\n }\n function read(key) {\n if (!host || typeof _S !== \"function\") return null;\n try { return _S(key); } catch (error) { return null; }\n }\n function text(value) {\n return typeof value === \"string\" && /\\S/.test(value) ? value : null;\n }\n function versionPart(value) {\n if (typeof value === \"number\") {\n if (!isFinite(value) || value < 0 || Math.floor(value) !== value) return null;\n value = String(value);\n }\n return typeof value === \"string\" && value.length > 0 && !/\\D/.test(value) ? value : null;\n }\n var standalone = read(\"standalone\");\n var language = text(read(\"lang\"));\n var major = versionPart(read(\"majorversion\"));\n var minor = versionPart(read(\"minorversion\"));\n var fullVersion = text(read(\"fullversion\"));\n return Object.freeze({\n web: web,\n host: host ? \"dsm\" : web ? \"standalone-web\" : \"unavailable\",\n dsmStandalone: typeof standalone === \"boolean\" ? standalone : null,\n dsmLanguage: language,\n appLocale: text(ctx.locale),\n systemVersion: major !== null && minor !== null ? major + \".\" + minor : null,\n fullVersion: fullVersion,\n theme: null\n });\n}\nfunction createHostAdapter(ctx) {\n ctx = ctx || {};\n var disposed = false, subscriptions = [];\n function launchApp(appId, config) {\n if (disposed) return { status: \"unavailable\" };\n if (typeof appId !== \"string\" || !appId || /\\s/.test(appId)) return { status: \"invalid-input\" };\n try {\n if (inspectHostPlatform(ctx).host !== \"dsm\") return { status: \"unsupported\" };\n var sds = SYNO.SDS, map = sds.Config && sds.Config.FnMap, notifier = sds.StatusNotifier;\n if (typeof sds.AppLaunch !== \"function\" || !map ||\n !Object.prototype.hasOwnProperty.call(map, appId) || !map[appId] || !notifier ||\n typeof notifier.isAppEnabled !== \"function\" || !notifier.isAppEnabled(appId) ||\n typeof notifier.isAppLaunchable !== \"function\" || !notifier.isAppLaunchable(appId)) {\n return { status: \"unsupported\" };\n }\n sds.AppLaunch(appId, config);\n return { status: \"requested\" };\n } catch (error) { return { status: \"failed\" }; }\n }\n function navigateFileStation(path, isDirectory) {\n if (disposed) return { status: \"unavailable\" };\n if (typeof path !== \"string\" || path.length > 4096 || path.charAt(0) !== \"/\" ||\n /[\\x00-\\x1f\\x7f\\\\]/.test(path)) return { status: \"invalid-input\" };\n var parts = path.split(\"/\");\n if (/^volume[0-9]+$/i.test(parts[1]) || parts.slice(1).some(function (part) {\n return !part || part === \".\" || part === \"..\";\n })) return { status: \"invalid-input\" };\n var result = launchApp(\"SYNO.SDS.App.FileStation3.Instance\", isDirectory ? { opendir: path } : { openfile: path });\n if (result.status !== \"requested\") return result;\n return { status: \"requested\", action: isDirectory ? \"locate-folder\" : \"highlight-file\" };\n }\n function unsupportedOperation() { return { status: disposed ? \"unavailable\" : \"unsupported\" }; }\n return Object.freeze({\n platform: function () {\n if (!disposed) return inspectHostPlatform(ctx);\n return Object.freeze({\n web: typeof window !== \"undefined\", host: \"unavailable\",\n dsmStandalone: null, dsmLanguage: null, appLocale: null,\n systemVersion: null, fullVersion: null, theme: null\n });\n },\n setTitle: function (title) {\n if (disposed) return { status: \"unavailable\" };\n if (typeof title !== \"string\") return { status: \"invalid-input\" };\n try {\n var platform = inspectHostPlatform(ctx);\n if (platform.host === \"dsm\") {\n if (typeof ctx.appWindow.setTitle !== \"function\") return { status: \"unsupported\" };\n ctx.appWindow.setTitle(title, undefined, true);\n return { status: \"ok\", target: \"dsm-window\" };\n }\n if (!platform.web) return { status: \"unavailable\" };\n if (typeof document === \"undefined\") return { status: \"unsupported\" };\n document.title = title;\n return { status: \"ok\", target: \"document\" };\n } catch (error) {\n return { status: \"failed\" };\n }\n },\n activateWindow: function () {\n if (disposed) return { status: \"unavailable\" };\n try {\n if (inspectHostPlatform(ctx).host !== \"dsm\" || typeof ctx.appWindow.activate !== \"function\") return { status: \"unsupported\" };\n ctx.appWindow.activate();\n return { status: \"requested\" };\n } catch (error) { return { status: \"failed\" }; }\n },\n requestClose: function () {\n if (disposed) return { status: \"unavailable\" };\n try {\n if (inspectHostPlatform(ctx).host !== \"dsm\") return { status: \"unsupported\" };\n var win = ctx.appWindow;\n if (typeof win.close !== \"function\" || typeof win.isStandaloneMainWindow !== \"function\" ||\n win.isStandaloneMainWindow() !== false) return { status: \"unsupported\" };\n if (win.canClose === false) return { status: \"blocked\" };\n win.close();\n return { status: \"requested\" };\n } catch (error) { return { status: \"failed\" }; }\n },\n openApp: function (appId) { return launchApp(appId, {}); },\n openURL: function (raw) {\n if (disposed || typeof window === \"undefined\") return { status: \"unavailable\" };\n if (typeof raw !== \"string\" || !raw || /[\\s\\x00-\\x1f\\x7f]/.test(raw)) return { status: \"invalid-input\" };\n if (typeof URL !== \"function\") return { status: \"unsupported\" };\n var url;\n try { url = new URL(raw); } catch (error) { return { status: \"invalid-input\" }; }\n if ((url.protocol !== \"http:\" && url.protocol !== \"https:\") || url.username || url.password) return { status: \"invalid-input\" };\n if (typeof window.open !== \"function\") return { status: \"unsupported\" };\n try {\n var opened = window.open(url.href, \"_blank\", \"noopener,noreferrer\");\n return { status: \"requested\", windowHandleAvailable: !!opened };\n } catch (error) { return { status: \"failed\" }; }\n },\n openFileManager: function (path) { return navigateFileStation(path, true); },\n revealFile: function (path) { return navigateFileStation(path, false); },\n openFile: unsupportedOperation,\n showFileDetails: unsupportedOperation,\n openAppSetting: unsupportedOperation,\n subscribe: function (event, listener) {\n if (disposed) return { status: \"unavailable\" };\n if (typeof listener !== \"function\") return { status: \"invalid-input\" };\n if (event !== \"window-resize\") return { status: \"unsupported\" };\n var win, callback = listener, active = true;\n function handler() { if (!disposed && callback) callback(); }\n function unsubscribe() {\n if (!active) return;\n active = false;\n callback = null;\n var target = win;\n win = null;\n var index = subscriptions.indexOf(unsubscribe);\n if (index !== -1) subscriptions.splice(index, 1);\n try { if (target) target.un(\"resize\", handler, target); } catch (error) {}\n }\n try {\n if (inspectHostPlatform(ctx).host !== \"dsm\") return { status: \"unsupported\" };\n win = ctx.appWindow;\n if (typeof win.on !== \"function\" || typeof win.un !== \"function\") return { status: \"unsupported\" };\n subscriptions.push(unsubscribe);\n win.on(\"resize\", handler, win);\n if (disposed) return { status: \"unavailable\" };\n return Object.freeze({ status: \"subscribed\", dispose: unsubscribe });\n } catch (error) {\n unsubscribe();\n return { status: \"failed\" };\n }\n },\n dispose: function () {\n if (disposed) return;\n disposed = true;\n subscriptions.slice().forEach(function (unsubscribe) { unsubscribe(); });\n ctx = null;\n }\n });\n}";
3
+ /** A static, per-package factory for independent web shells; no Ext or transport. */
4
+ export declare function renderHostAdapterScript(packageId: string): string;
5
+ export declare function hostScriptName(build: number): string;
@@ -0,0 +1,148 @@
1
+ import { HOST_PLATFORM_RUNTIME } from "./host-platform.js";
2
+ export const HOST_ADAPTER_RUNTIME = `${HOST_PLATFORM_RUNTIME}
3
+ function createHostAdapter(ctx) {
4
+ ctx = ctx || {};
5
+ var disposed = false, subscriptions = [];
6
+ function launchApp(appId, config) {
7
+ if (disposed) return { status: "unavailable" };
8
+ if (typeof appId !== "string" || !appId || /\\s/.test(appId)) return { status: "invalid-input" };
9
+ try {
10
+ if (inspectHostPlatform(ctx).host !== "dsm") return { status: "unsupported" };
11
+ var sds = SYNO.SDS, map = sds.Config && sds.Config.FnMap, notifier = sds.StatusNotifier;
12
+ if (typeof sds.AppLaunch !== "function" || !map ||
13
+ !Object.prototype.hasOwnProperty.call(map, appId) || !map[appId] || !notifier ||
14
+ typeof notifier.isAppEnabled !== "function" || !notifier.isAppEnabled(appId) ||
15
+ typeof notifier.isAppLaunchable !== "function" || !notifier.isAppLaunchable(appId)) {
16
+ return { status: "unsupported" };
17
+ }
18
+ sds.AppLaunch(appId, config);
19
+ return { status: "requested" };
20
+ } catch (error) { return { status: "failed" }; }
21
+ }
22
+ function navigateFileStation(path, isDirectory) {
23
+ if (disposed) return { status: "unavailable" };
24
+ if (typeof path !== "string" || path.length > 4096 || path.charAt(0) !== "/" ||
25
+ /[\\x00-\\x1f\\x7f\\\\]/.test(path)) return { status: "invalid-input" };
26
+ var parts = path.split("/");
27
+ if (/^volume[0-9]+$/i.test(parts[1]) || parts.slice(1).some(function (part) {
28
+ return !part || part === "." || part === "..";
29
+ })) return { status: "invalid-input" };
30
+ var result = launchApp("SYNO.SDS.App.FileStation3.Instance", isDirectory ? { opendir: path } : { openfile: path });
31
+ if (result.status !== "requested") return result;
32
+ return { status: "requested", action: isDirectory ? "locate-folder" : "highlight-file" };
33
+ }
34
+ function unsupportedOperation() { return { status: disposed ? "unavailable" : "unsupported" }; }
35
+ return Object.freeze({
36
+ platform: function () {
37
+ if (!disposed) return inspectHostPlatform(ctx);
38
+ return Object.freeze({
39
+ web: typeof window !== "undefined", host: "unavailable",
40
+ dsmStandalone: null, dsmLanguage: null, appLocale: null,
41
+ systemVersion: null, fullVersion: null, theme: null
42
+ });
43
+ },
44
+ setTitle: function (title) {
45
+ if (disposed) return { status: "unavailable" };
46
+ if (typeof title !== "string") return { status: "invalid-input" };
47
+ try {
48
+ var platform = inspectHostPlatform(ctx);
49
+ if (platform.host === "dsm") {
50
+ if (typeof ctx.appWindow.setTitle !== "function") return { status: "unsupported" };
51
+ ctx.appWindow.setTitle(title, undefined, true);
52
+ return { status: "ok", target: "dsm-window" };
53
+ }
54
+ if (!platform.web) return { status: "unavailable" };
55
+ if (typeof document === "undefined") return { status: "unsupported" };
56
+ document.title = title;
57
+ return { status: "ok", target: "document" };
58
+ } catch (error) {
59
+ return { status: "failed" };
60
+ }
61
+ },
62
+ activateWindow: function () {
63
+ if (disposed) return { status: "unavailable" };
64
+ try {
65
+ if (inspectHostPlatform(ctx).host !== "dsm" || typeof ctx.appWindow.activate !== "function") return { status: "unsupported" };
66
+ ctx.appWindow.activate();
67
+ return { status: "requested" };
68
+ } catch (error) { return { status: "failed" }; }
69
+ },
70
+ requestClose: function () {
71
+ if (disposed) return { status: "unavailable" };
72
+ try {
73
+ if (inspectHostPlatform(ctx).host !== "dsm") return { status: "unsupported" };
74
+ var win = ctx.appWindow;
75
+ if (typeof win.close !== "function" || typeof win.isStandaloneMainWindow !== "function" ||
76
+ win.isStandaloneMainWindow() !== false) return { status: "unsupported" };
77
+ if (win.canClose === false) return { status: "blocked" };
78
+ win.close();
79
+ return { status: "requested" };
80
+ } catch (error) { return { status: "failed" }; }
81
+ },
82
+ openApp: function (appId) { return launchApp(appId, {}); },
83
+ openURL: function (raw) {
84
+ if (disposed || typeof window === "undefined") return { status: "unavailable" };
85
+ if (typeof raw !== "string" || !raw || /[\\s\\x00-\\x1f\\x7f]/.test(raw)) return { status: "invalid-input" };
86
+ if (typeof URL !== "function") return { status: "unsupported" };
87
+ var url;
88
+ try { url = new URL(raw); } catch (error) { return { status: "invalid-input" }; }
89
+ if ((url.protocol !== "http:" && url.protocol !== "https:") || url.username || url.password) return { status: "invalid-input" };
90
+ if (typeof window.open !== "function") return { status: "unsupported" };
91
+ try {
92
+ var opened = window.open(url.href, "_blank", "noopener,noreferrer");
93
+ return { status: "requested", windowHandleAvailable: !!opened };
94
+ } catch (error) { return { status: "failed" }; }
95
+ },
96
+ openFileManager: function (path) { return navigateFileStation(path, true); },
97
+ revealFile: function (path) { return navigateFileStation(path, false); },
98
+ openFile: unsupportedOperation,
99
+ showFileDetails: unsupportedOperation,
100
+ openAppSetting: unsupportedOperation,
101
+ subscribe: function (event, listener) {
102
+ if (disposed) return { status: "unavailable" };
103
+ if (typeof listener !== "function") return { status: "invalid-input" };
104
+ if (event !== "window-resize") return { status: "unsupported" };
105
+ var win, callback = listener, active = true;
106
+ function handler() { if (!disposed && callback) callback(); }
107
+ function unsubscribe() {
108
+ if (!active) return;
109
+ active = false;
110
+ callback = null;
111
+ var target = win;
112
+ win = null;
113
+ var index = subscriptions.indexOf(unsubscribe);
114
+ if (index !== -1) subscriptions.splice(index, 1);
115
+ try { if (target) target.un("resize", handler, target); } catch (error) {}
116
+ }
117
+ try {
118
+ if (inspectHostPlatform(ctx).host !== "dsm") return { status: "unsupported" };
119
+ win = ctx.appWindow;
120
+ if (typeof win.on !== "function" || typeof win.un !== "function") return { status: "unsupported" };
121
+ subscriptions.push(unsubscribe);
122
+ win.on("resize", handler, win);
123
+ if (disposed) return { status: "unavailable" };
124
+ return Object.freeze({ status: "subscribed", dispose: unsubscribe });
125
+ } catch (error) {
126
+ unsubscribe();
127
+ return { status: "failed" };
128
+ }
129
+ },
130
+ dispose: function () {
131
+ if (disposed) return;
132
+ disposed = true;
133
+ subscriptions.slice().forEach(function (unsubscribe) { unsubscribe(); });
134
+ ctx = null;
135
+ }
136
+ });
137
+ }`;
138
+ /** A static, per-package factory for independent web shells; no Ext or transport. */
139
+ export function renderHostAdapterScript(packageId) {
140
+ return `(function () {
141
+ ${HOST_ADAPTER_RUNTIME}
142
+ window.DsmPackageHosts = window.DsmPackageHosts || {};
143
+ window.DsmPackageHosts[${JSON.stringify(packageId)}] = createHostAdapter;
144
+ })();\n`;
145
+ }
146
+ export function hostScriptName(build) {
147
+ return `host-${String(build).padStart(4, "0")}.js`;
148
+ }
@@ -0,0 +1,13 @@
1
+ /** Read-only, per-call browser platform observation; never an authorization proof. */
2
+ export interface HostPlatformSnapshot {
3
+ readonly web: boolean;
4
+ readonly host: "dsm" | "standalone-web" | "unavailable";
5
+ readonly dsmStandalone: boolean | null;
6
+ readonly dsmLanguage: string | null;
7
+ readonly appLocale: string | null;
8
+ readonly systemVersion: string | null;
9
+ readonly fullVersion: string | null;
10
+ readonly theme: null;
11
+ }
12
+ /** ES5, self-contained browser runtime, following I18N_RUNTIME's embedding pattern. */
13
+ export declare const HOST_PLATFORM_RUNTIME = "function inspectHostPlatform(ctx) {\n ctx = ctx || {};\n var web = typeof window !== \"undefined\";\n var host = false;\n try {\n host = !!(web && ctx.appWindow && typeof SYNO !== \"undefined\" &&\n SYNO.SDS && typeof SYNO.SDS.AppWindow === \"function\" &&\n ctx.appWindow instanceof SYNO.SDS.AppWindow);\n } catch (error) {\n // An unsupported host constructor must not break a standalone shell.\n }\n function read(key) {\n if (!host || typeof _S !== \"function\") return null;\n try { return _S(key); } catch (error) { return null; }\n }\n function text(value) {\n return typeof value === \"string\" && /\\S/.test(value) ? value : null;\n }\n function versionPart(value) {\n if (typeof value === \"number\") {\n if (!isFinite(value) || value < 0 || Math.floor(value) !== value) return null;\n value = String(value);\n }\n return typeof value === \"string\" && value.length > 0 && !/\\D/.test(value) ? value : null;\n }\n var standalone = read(\"standalone\");\n var language = text(read(\"lang\"));\n var major = versionPart(read(\"majorversion\"));\n var minor = versionPart(read(\"minorversion\"));\n var fullVersion = text(read(\"fullversion\"));\n return Object.freeze({\n web: web,\n host: host ? \"dsm\" : web ? \"standalone-web\" : \"unavailable\",\n dsmStandalone: typeof standalone === \"boolean\" ? standalone : null,\n dsmLanguage: language,\n appLocale: text(ctx.locale),\n systemVersion: major !== null && minor !== null ? major + \".\" + minor : null,\n fullVersion: fullVersion,\n theme: null\n });\n}";
@@ -0,0 +1,42 @@
1
+ /** ES5, self-contained browser runtime, following I18N_RUNTIME's embedding pattern. */
2
+ export const HOST_PLATFORM_RUNTIME = `function inspectHostPlatform(ctx) {
3
+ ctx = ctx || {};
4
+ var web = typeof window !== "undefined";
5
+ var host = false;
6
+ try {
7
+ host = !!(web && ctx.appWindow && typeof SYNO !== "undefined" &&
8
+ SYNO.SDS && typeof SYNO.SDS.AppWindow === "function" &&
9
+ ctx.appWindow instanceof SYNO.SDS.AppWindow);
10
+ } catch (error) {
11
+ // An unsupported host constructor must not break a standalone shell.
12
+ }
13
+ function read(key) {
14
+ if (!host || typeof _S !== "function") return null;
15
+ try { return _S(key); } catch (error) { return null; }
16
+ }
17
+ function text(value) {
18
+ return typeof value === "string" && /\\S/.test(value) ? value : null;
19
+ }
20
+ function versionPart(value) {
21
+ if (typeof value === "number") {
22
+ if (!isFinite(value) || value < 0 || Math.floor(value) !== value) return null;
23
+ value = String(value);
24
+ }
25
+ return typeof value === "string" && value.length > 0 && !/\\D/.test(value) ? value : null;
26
+ }
27
+ var standalone = read("standalone");
28
+ var language = text(read("lang"));
29
+ var major = versionPart(read("majorversion"));
30
+ var minor = versionPart(read("minorversion"));
31
+ var fullVersion = text(read("fullversion"));
32
+ return Object.freeze({
33
+ web: web,
34
+ host: host ? "dsm" : web ? "standalone-web" : "unavailable",
35
+ dsmStandalone: typeof standalone === "boolean" ? standalone : null,
36
+ dsmLanguage: language,
37
+ appLocale: text(ctx.locale),
38
+ systemVersion: major !== null && minor !== null ? major + "." + minor : null,
39
+ fullVersion: fullVersion,
40
+ theme: null
41
+ });
42
+ }`;
@@ -0,0 +1,49 @@
1
+ import type { HostPlatformSnapshot } from "./host-platform.js";
2
+ export type HostTitleResult = {
3
+ status: "ok";
4
+ target: "dsm-window" | "document";
5
+ } | {
6
+ status: "unsupported" | "unavailable" | "invalid-input" | "failed";
7
+ };
8
+ export type HostRequestResult = {
9
+ status: "requested";
10
+ } | {
11
+ status: "unsupported" | "unavailable" | "invalid-input" | "blocked" | "failed";
12
+ };
13
+ export type HostURLResult = {
14
+ status: "requested";
15
+ windowHandleAvailable: boolean;
16
+ } | {
17
+ status: "unsupported" | "unavailable" | "invalid-input" | "failed";
18
+ };
19
+ export type HostEvent = "window-resize" | "host-theme-change" | "host-language-change";
20
+ export type HostSubscriptionResult = {
21
+ status: "subscribed";
22
+ dispose(): void;
23
+ } | {
24
+ status: "unsupported" | "unavailable" | "invalid-input" | "failed";
25
+ };
26
+ export type HostFileNavigationResult = {
27
+ status: "requested";
28
+ action: "locate-folder" | "highlight-file";
29
+ } | {
30
+ status: "unsupported" | "unavailable" | "invalid-input" | "failed";
31
+ };
32
+ export type HostUnsupportedResult = {
33
+ status: "unsupported" | "unavailable";
34
+ };
35
+ export interface HostAdapter {
36
+ platform(): HostPlatformSnapshot;
37
+ setTitle(title: string): HostTitleResult;
38
+ activateWindow(): HostRequestResult;
39
+ requestClose(): HostRequestResult;
40
+ openApp(appId: string): HostRequestResult;
41
+ openURL(url: string): HostURLResult;
42
+ openFileManager(path: string): HostFileNavigationResult;
43
+ revealFile(path: string): HostFileNavigationResult;
44
+ openFile(path: string): HostUnsupportedResult;
45
+ showFileDetails(paths: readonly string[]): HostUnsupportedResult;
46
+ openAppSetting(): HostUnsupportedResult;
47
+ subscribe(event: HostEvent, listener: () => void): HostSubscriptionResult;
48
+ dispose(): void;
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export interface IntegrationInspection {
2
+ protocol: 1 | 2;
3
+ dataAccess: "unavailable" | "relaxed";
4
+ }
5
+ /** Artifact consistency only; bytes, approval and health never certify NAS access. */
6
+ export declare function inspectHelper(dir: string, info: Record<string, string>, findings: string[], daemon?: boolean): Promise<IntegrationInspection | undefined>;
@@ -0,0 +1,88 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import * as tar from "tar";
4
+ import { windowScriptName } from "./dsm-window.js";
5
+ import { helperOps } from "./helper.js";
6
+ import { sha256 } from "./helper-inventory.js";
7
+ import { renderStandaloneScript } from "./lifecycle.js";
8
+ import { rootTasks } from "./root-window.js";
9
+ /** Artifact consistency only; bytes, approval and health never certify NAS access. */
10
+ export async function inspectHelper(dir, info, findings, daemon = false) {
11
+ const files = new Map();
12
+ const windowPath = `ui/${windowScriptName({ build: Number(info.version?.split("-").at(-1)) })}`;
13
+ const windows = [];
14
+ let oversized = false;
15
+ await tar.t({ file: join(dir, "package.tgz"), onReadEntry(entry) {
16
+ const path = entry.path.replace(/^\.\//, "");
17
+ // Enumerate candidates to reject extras; only the INFO-derived exact path is read.
18
+ if (/^ui\/window-[^/]*\.js$/.test(path))
19
+ windows.push(path);
20
+ if (entry.type !== "File" || !(path.startsWith("integration/workers/") || ["integration/helper", "integration/inventory.json", "integration/workers.json", "ui/cgi/api.cgi", windowPath].includes(path)))
21
+ return;
22
+ const limit = ["integration/helper", "ui/cgi/api.cgi"].includes(path) ? 64 * 1024 * 1024 : 1024 * 1024;
23
+ if (files.has(path) || entry.size > limit) {
24
+ oversized = true;
25
+ return;
26
+ }
27
+ const chunks = [];
28
+ let size = 0;
29
+ entry.on("data", (chunk) => { size += chunk.length; if (size <= limit)
30
+ chunks.push(chunk);
31
+ else
32
+ oversized = true; });
33
+ entry.on("end", () => files.set(path, Buffer.concat(chunks)));
34
+ } });
35
+ try {
36
+ if (oversized)
37
+ throw new Error("oversized/duplicate payload");
38
+ const bytes = (path) => { const b = files.get(path); if (!b)
39
+ throw new Error(`missing ${path}`); return b; };
40
+ const raw = bytes("integration/inventory.json").toString("utf8");
41
+ const i = JSON.parse(raw);
42
+ const keys = i.dataAccess === undefined ? "package,payloadDigest,protocol,registryDigest,version" : "dataAccess,package,payloadDigest,protocol,registryDigest,version";
43
+ if (Object.keys(i).sort().join(",") !== keys || (i.dataAccess !== undefined && (i.protocol !== 2 || !["unavailable", "relaxed"].includes(i.dataAccess))) ||
44
+ i.package !== info.package || i.version !== info.version || !/^[A-Za-z][A-Za-z0-9]{1,63}$/.test(i.package) || !/^\d+(\.\d+){0,3}-\d{4}$/.test(i.version) || ![1, 2].includes(i.protocol) ||
45
+ !/^[a-f0-9]{64}$/.test(i.payloadDigest) || !/^[a-f0-9]{64}$/.test(i.registryDigest) || JSON.stringify(i) !== raw)
46
+ throw new Error("invalid inventory");
47
+ if (windows.length !== 1 || windows[0] !== windowPath)
48
+ throw new Error("missing/ambiguous window or build mismatch");
49
+ const helper = bytes("integration/helper");
50
+ if (sha256(helper) !== i.payloadDigest || !helper.includes(Buffer.from(JSON.stringify({ ...i, payloadDigest: "" }))) ||
51
+ !bytes("ui/cgi/api.cgi").includes(Buffer.from(raw)))
52
+ throw new Error("payload/compiled inventory mismatch");
53
+ const workers = bytes("integration/workers.json");
54
+ const registry = JSON.parse(workers.toString("utf8"));
55
+ if (!Array.isArray(registry) || registry.length > 16 || sha256(workers) !== i.registryDigest || JSON.stringify(registry) !== workers.toString("utf8"))
56
+ throw new Error("invalid registry");
57
+ let previous = "", total = 0;
58
+ for (const worker of registry) {
59
+ if (!worker || Object.keys(worker).sort().join(",") !== "digest,id" || typeof worker.id !== "string" ||
60
+ !/^[a-z][a-z0-9_]{0,31}$/.test(worker.id) || (previous && previous.localeCompare(worker.id, "en") >= 0) ||
61
+ !/^[a-f0-9]{64}$/.test(worker.digest))
62
+ throw new Error("invalid worker");
63
+ const content = bytes(`integration/workers/${worker.id}`);
64
+ total += content.length;
65
+ if (total > 1024 * 1024 || sha256(content) !== worker.digest)
66
+ throw new Error("worker digest mismatch");
67
+ previous = worker.id;
68
+ }
69
+ if ([...files.keys()].filter(p => p.startsWith("integration/workers/")).length !== registry.length)
70
+ throw new Error("unregistered worker");
71
+ const manifest = { package: i.package, spkVersion: i.version, integration: { protocol: i.protocol, workers: {} } };
72
+ const tasks = JSON.parse(/\.rootTasks = (\[[^\n]+\]);/.exec(bytes(windowPath).toString("utf8"))?.[1] ?? "null");
73
+ const expected = rootTasks({ ...manifest, rootOps: helperOps(manifest, i) });
74
+ if (!Array.isArray(tasks) || JSON.stringify(tasks.filter(t => t.op?.startsWith("helper_"))) !== JSON.stringify(expected))
75
+ throw new Error("approved task mismatch");
76
+ // Daemon lifecycle is verified separately with the complete daemon config.
77
+ if (!daemon)
78
+ for (const hook of ["start-stop-status", "preupgrade", "preuninst"]) {
79
+ if (readFileSync(join(dir, "scripts", hook), "utf8") !== renderStandaloneScript(hook, manifest))
80
+ throw new Error("lifecycle mismatch");
81
+ }
82
+ return { protocol: i.protocol, dataAccess: i.dataAccess ?? "unavailable" };
83
+ }
84
+ catch {
85
+ findings.push("integration inventory、digest、compiled binding、worker 或 lifecycle 不符合固定契約");
86
+ return undefined;
87
+ }
88
+ }
@@ -1,3 +1,4 @@
1
+ import { type IntegrationInspection } from "./inspect-helper.js";
1
2
  export type SpkKind = "native-go" | "binary" | "container";
2
3
  export interface SpkInspection {
3
4
  kind?: SpkKind;
@@ -8,6 +9,7 @@ export interface SpkInspection {
8
9
  signed: boolean;
9
10
  /** Payload path of the package CGI, when present with mode 0755. */
10
11
  cgi?: string;
12
+ integration?: IntegrationInspection;
11
13
  payload: Array<{
12
14
  path: string;
13
15
  mode: string;
@@ -3,6 +3,9 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import * as tar from "tar";
5
5
  import { CGI_PAYLOAD_PATH } from "./dsm-window.js";
6
+ import { daemonConfig, readDaemon } from "./daemon.js";
7
+ import { renderStandaloneScript } from "./lifecycle.js";
8
+ import { inspectHelper } from "./inspect-helper.js";
6
9
  const ROOT_SCRIPT_HINTS = [/\bsudo\b/, /\bchown\s+(-\w+\s+)*root\b/, /chmod\s+[ugo]*\+s\b/, /chmod\s+[2467][0-7]{3}\b/];
7
10
  /** Reads an SPK without installing it and reports how it would run on DSM. */
8
11
  export async function inspectStandaloneSpk(spkPath) {
@@ -20,8 +23,20 @@ export async function inspectSpk(spkPath, expectedKind) {
20
23
  : paths.has("native.yaml") ? "binary" : paths.has("compose.yaml") ? "container" : "native-go");
21
24
  const identityPayload = [CGI_PAYLOAD_PATH, "integration/helper", "integration/inventory.json", "integration/workers.json", "integration/identity/app.mjs"].every(path => paths.has(path));
22
25
  const info = readInfo(join(dir, "INFO"), findings, kind, identityPayload);
26
+ if (kind === "native-go" && paths.has("integration/helper") && info.precheckstartstop !== "yes") {
27
+ findings.push('integration INFO 必須明確設定 precheckstartstop="yes",否則 DSM 不執行 prestop');
28
+ }
23
29
  checkConf(dir, findings, kind);
24
30
  checkScripts(dir, findings);
31
+ if (kind === "native-go")
32
+ await checkDaemon(dir, info, paths, payload, findings);
33
+ let integration;
34
+ if ([...paths].some(path => path.startsWith("integration/"))) {
35
+ if (kind !== "native-go" || payload.find(p => p.path === "integration/helper")?.mode !== "0755")
36
+ findings.push("integration 必須有 native-go executable helper");
37
+ else if (!findings.length)
38
+ integration = await inspectHelper(dir, info, findings, paths.has("daemon/config.json"));
39
+ }
25
40
  const cgiEntry = payload.find((entry) => entry.path === CGI_PAYLOAD_PATH);
26
41
  if (kind === "native-go" && !cgiEntry) {
27
42
  findings.push(`payload 沒有 ${CGI_PAYLOAD_PATH}`);
@@ -37,6 +52,7 @@ export async function inspectSpk(spkPath, expectedKind) {
37
52
  signed: existsSync(join(dir, "syno_signature.asc")),
38
53
  cgi: cgiEntry?.mode === "0755" ? CGI_PAYLOAD_PATH : undefined,
39
54
  payload,
55
+ ...(integration ? { integration } : {}),
40
56
  findings,
41
57
  };
42
58
  }
@@ -44,6 +60,39 @@ export async function inspectSpk(spkPath, expectedKind) {
44
60
  rmSync(dir, { recursive: true, force: true });
45
61
  }
46
62
  }
63
+ async function checkDaemon(dir, info, paths, payload, findings) {
64
+ if (![...paths].some(path => path.startsWith("daemon/")))
65
+ return;
66
+ if (info.precheckstartstop !== "yes")
67
+ findings.push('daemon INFO 必須明確設定 precheckstartstop="yes"');
68
+ if (payload.find(p => p.path === "daemon/control")?.mode !== "0755")
69
+ findings.push("daemon/control 缺少或非 executable");
70
+ const unpacked = join(dir, "daemon-inspection");
71
+ // Extract only the bounded config; the payload executable is never run.
72
+ const { mkdirSync } = await import("node:fs");
73
+ mkdirSync(unpacked);
74
+ await tar.x({ file: join(dir, "package.tgz"), cwd: unpacked, filter: path => path.replace(/^\.\//, "") === "daemon/config.json" });
75
+ try {
76
+ const config = JSON.parse(readFileSync(join(unpacked, "daemon/config.json"), "utf8"));
77
+ if (config.package !== info.package || Object.keys(config).some(key => !["package", "exec", "args", "env", "timeouts", "uninstall_args"].includes(key)))
78
+ throw new Error("package/config mismatch");
79
+ const daemon = readDaemon({ exec: config.exec, args: config.args, uninstall_args: config.uninstall_args, env: config.env, timeouts: config.timeouts, targets: { x86_64: "payload" } }, dir, ["x86_64"], () => { });
80
+ if (!daemon)
81
+ throw new Error("invalid config");
82
+ if (payload.find(p => p.path === `daemon/payload/${daemon.exec}`)?.mode !== "0755")
83
+ throw new Error("missing executable");
84
+ const manifest = { package: info.package, daemon, ...(paths.has("integration/helper") ? { integration: {} } : {}) };
85
+ if (readFileSync(join(unpacked, "daemon/config.json"), "utf8") !== daemonConfig(manifest))
86
+ throw new Error("non-canonical daemon config");
87
+ for (const hook of ["start-stop-status", "preupgrade", "preuninst"]) {
88
+ if (readFileSync(join(dir, "scripts", hook), "utf8") !== renderStandaloneScript(hook, manifest))
89
+ findings.push(`daemon scripts/${hook} 與固定 lifecycle 不符`);
90
+ }
91
+ }
92
+ catch {
93
+ findings.push("daemon/config.json 或 payload 不符合固定 daemon 契約");
94
+ }
95
+ }
47
96
  function readInfo(path, findings, kind, identityPayload) {
48
97
  if (!existsSync(path)) {
49
98
  findings.push("缺少 INFO");
@@ -0,0 +1,2 @@
1
+ /** Non-privileged management veto shared by the generated package hooks. */
2
+ export declare function renderHelperStoppedCheck(packageId: string): string;
@@ -0,0 +1,12 @@
1
+ /** Non-privileged management veto shared by the generated package hooks. */
2
+ export function renderHelperStoppedCheck(packageId) {
3
+ return `/var/packages/${packageId}/target/integration/helper check-stopped || {
4
+ message='Run the approved helper_teardown or helper_disable operation before stopping, upgrading or uninstalling.'
5
+ printf '%s\\n' "$message" >&2
6
+ if [ -n "\${SYNOPKG_TEMP_LOGFILE:-}" ]; then
7
+ (printf '%s\\n' "$message" >> "$SYNOPKG_TEMP_LOGFILE") 2>/dev/null || :
8
+ fi
9
+ exit 1
10
+ }
11
+ `;
12
+ }
@@ -0,0 +1,3 @@
1
+ import type { StandaloneManifest } from "./manifest.js";
2
+ /** Non-privileged hooks: precheck gates DSM management; no hook tears down root. */
3
+ export declare function renderStandaloneScript(name: string, manifest: StandaloneManifest): string;
@@ -0,0 +1,53 @@
1
+ import { renderHelperStoppedCheck } from "./lifecycle-helper.js";
2
+ /** Non-privileged hooks: precheck gates DSM management; no hook tears down root. */
3
+ export function renderStandaloneScript(name, manifest) {
4
+ if (manifest.daemon)
5
+ return renderDaemonScript(name, manifest);
6
+ if (manifest.integration && ["preupgrade", "preuninst", "start-stop-status"].includes(name)) {
7
+ const check = renderHelperStoppedCheck(manifest.package);
8
+ if (name !== "start-stop-status")
9
+ return `#!/bin/sh\n${check}`;
10
+ return `#!/bin/sh
11
+ case "$1" in
12
+ # Start the ordinary app even when integration is disabled or needs cleanup.
13
+ # No root enable/recover here; the administrator must explicitly approve it.
14
+ prestart|start|status) exit 0 ;;
15
+ prestop|stop)
16
+ # prestop is the management veto. stop runs inside ExecStop: its failure
17
+ # reports unsafe residue but cannot keep the DSM unit active.
18
+ ${check} ;;
19
+ *) exit 1 ;;
20
+ esac
21
+ `;
22
+ }
23
+ if (name === "start-stop-status") {
24
+ return `#!/bin/sh
25
+ case "$1" in
26
+ start|stop|status) exit 0 ;;
27
+ *) exit 1 ;;
28
+ esac
29
+ `;
30
+ }
31
+ return "#!/bin/sh\nexit 0\n";
32
+ }
33
+ function renderDaemonScript(name, manifest) {
34
+ const pkg = manifest.package;
35
+ const control = `"/var/packages/${pkg}/target/daemon/control"`;
36
+ const paths = `"\${SYNOPKG_PKGDEST:-/var/packages/${pkg}/target}" "\${SYNOPKG_PKGVAR:-/var/packages/${pkg}/var}"`;
37
+ const check = manifest.integration ? renderHelperStoppedCheck(pkg) : "";
38
+ if (["preupgrade", "preuninst"].includes(name))
39
+ return `#!/bin/sh\n${check}exec ${control} ${name === "preuninst" && manifest.daemon?.uninstall_args ? "uninstall" : "stop"} ${paths}\n`;
40
+ if (name !== "start-stop-status")
41
+ return "#!/bin/sh\nexit 0\n";
42
+ return `#!/bin/sh
43
+ case "$1" in
44
+ prestart) exit 0 ;;
45
+ prestop)
46
+ ${check} exit 0 ;;
47
+ start|status) exec ${control} "$1" ${paths} ;;
48
+ stop|restart)
49
+ ${check} exec ${control} "$1" ${paths} ;;
50
+ *) exit 1 ;;
51
+ esac
52
+ `;
53
+ }
@@ -2,6 +2,7 @@ import { type NativeArchFamily } from "../build-spk/dsm-arch.js";
2
2
  import { type Catalogs, type Locale } from "./i18n.js";
3
3
  import { type RootOp } from "./root-ops.js";
4
4
  import { type Integration } from "./helper.js";
5
+ import { type Daemon } from "./daemon.js";
5
6
  /**
6
7
  * `dsm-package.yaml`: a Standalone Package, i.e. an unsigned native SPK that
7
8
  * runs as its own DSM package account with a CGI backend and a DSM window UI.
@@ -51,6 +52,7 @@ export interface StandaloneManifest {
51
52
  /** `root/<name>.sh`, present exactly when privilege is root. */
52
53
  rootOps?: RootOp[];
53
54
  integration?: Integration;
55
+ daemon?: Daemon;
54
56
  }
55
57
  export interface StandaloneError {
56
58
  code: string;