@underpostnet/cyberia 3.3.0 → 3.3.76

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 (568) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +17 -5
  3. package/.github/workflows/coverall.cyberia.ci.yml +39 -14
  4. package/.github/workflows/cyberia-client.cd.yml +5 -0
  5. package/.github/workflows/cyberia-server.cd.yml +5 -0
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +35 -20
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +1 -0
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +96 -0
  22. package/CHANGELOG.md +653 -2513
  23. package/CLI-HELP.md +323 -157
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +5 -5
  27. package/README.md +48 -28
  28. package/bin/build.js +86 -41
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +1494 -1403
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +1494 -1403
  33. package/compose.env +17 -5
  34. package/conf.js +98 -7
  35. package/deploy/cyberia-client/deploy.sh +46 -0
  36. package/deploy/cyberia-client/package.sh +17 -0
  37. package/deploy/cyberia-client/state.sh +15 -0
  38. package/deploy/cyberia-server/deploy.sh +46 -0
  39. package/deploy/cyberia-server/package.sh +17 -0
  40. package/deploy/cyberia-server/state.sh +15 -0
  41. package/deploy/dd-cyberia/deploy.sh +9 -29
  42. package/deploy/dd-cyberia/init.sh +38 -45
  43. package/deploy/dd-cyberia/package.sh +16 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +133 -147
  46. package/deploy/lib/config.sh +29 -0
  47. package/deploy/lib/github-actions-logging.sh +610 -0
  48. package/deploy/lib/host.sh +194 -0
  49. package/deploy/lib/state.sh +65 -0
  50. package/deploy/pwa-microservices-template/deploy.sh +17 -42
  51. package/deploy/release/deploy.sh +9 -41
  52. package/deployment.yaml +7 -2
  53. package/docker-compose.yml +23 -17
  54. package/examples/static-page/README.md +19 -19
  55. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  56. package/hardhat/hardhat.config.js +1 -6
  57. package/hardhat/package-lock.json +101 -40
  58. package/hardhat/package.json +2 -2
  59. package/manifests/alertmanager/deployment.yaml +81 -0
  60. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  61. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  62. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  63. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  64. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  65. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  66. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  67. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  68. package/manifests/deployment/mongo-express/service.yaml +16 -0
  69. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  70. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  71. package/manifests/grafana/deployment.yaml +26 -7
  72. package/manifests/grafana/kustomization.yaml +1 -0
  73. package/manifests/grafana/service.yaml +6 -1
  74. package/manifests/mariadb/pv.yaml +1 -0
  75. package/manifests/mongodb/statefulset.yaml +9 -2
  76. package/manifests/mysql/pv-pvc.yaml +14 -1
  77. package/manifests/postgresql/pv.yaml +12 -1
  78. package/manifests/postgresql/pvc.yaml +1 -0
  79. package/manifests/prometheus/deployment.yaml +18 -22
  80. package/package.json +87 -12
  81. package/scripts/event-monitor.sh +56 -0
  82. package/scripts/k3s-node-setup.sh +27 -14
  83. package/scripts/kubeadm-node-setup.sh +31 -11
  84. package/scripts/maas-setup.sh +8 -8
  85. package/scripts/selinux-normalize.sh +907 -0
  86. package/scripts/shutdown-machine.sh +843 -0
  87. package/scripts/test-monitor.sh +5 -5
  88. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +3 -3
  99. package/src/api/cyberia-action/cyberia-action.service.js +5 -6
  100. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  101. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  102. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  103. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  104. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
  105. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  106. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
  107. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
  108. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  109. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  110. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  111. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  112. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  113. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  114. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  115. package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
  116. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  117. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  118. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
  119. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
  120. package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
  121. package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
  122. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  123. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  124. package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
  125. package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
  126. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  127. package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
  128. package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
  129. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  130. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  131. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  132. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  133. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
  134. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  135. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  136. package/src/api/cyberia-map/cyberia-map.service.js +8 -4
  137. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  138. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  139. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  140. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  141. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  142. package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
  143. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  144. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  145. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  146. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  147. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  148. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  149. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  150. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  151. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
  152. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  153. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  154. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  155. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  156. package/src/api/default/default.controller.js +1 -1
  157. package/src/api/default/default.router.js +1 -1
  158. package/src/api/default/default.service.js +2 -2
  159. package/src/api/document/document.controller.js +1 -1
  160. package/src/api/document/document.router.js +1 -1
  161. package/src/api/document/document.service.js +2 -2
  162. package/src/api/file/file.controller.js +1 -1
  163. package/src/api/file/file.ref.js +153 -0
  164. package/src/api/file/file.ref.json +11 -2
  165. package/src/api/file/file.router.js +1 -1
  166. package/src/api/file/file.service.js +2 -2
  167. package/src/api/instance/instance.controller.js +1 -1
  168. package/src/api/instance/instance.router.js +1 -1
  169. package/src/api/instance/instance.service.js +2 -2
  170. package/src/api/ipfs/ipfs.controller.js +1 -1
  171. package/src/api/ipfs/ipfs.router.js +2 -2
  172. package/src/api/ipfs/ipfs.service.js +2 -2
  173. package/src/api/object-layer/object-layer.controller.js +1 -1
  174. package/src/api/object-layer/object-layer.model.js +47 -12
  175. package/src/api/object-layer/object-layer.router.js +12 -5
  176. package/src/api/object-layer/object-layer.service.js +116 -123
  177. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  178. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  179. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  180. package/src/api/test/test.controller.js +1 -1
  181. package/src/api/test/test.service.js +1 -1
  182. package/src/api/user/guest.service.js +3 -3
  183. package/src/api/user/user.controller.js +1 -1
  184. package/src/api/user/user.router.js +2 -2
  185. package/src/api/user/user.service.js +4 -4
  186. package/src/api.js +5 -9
  187. package/src/cli/app.js +402 -0
  188. package/src/cli/baremetal.js +30 -35
  189. package/src/cli/client.js +306 -0
  190. package/src/cli/cloud-init.js +3 -3
  191. package/src/cli/cluster.js +191 -82
  192. package/src/cli/db.js +120 -68
  193. package/src/cli/deploy.js +157 -75
  194. package/src/cli/docker-compose.js +5 -5
  195. package/src/cli/domains.js +184 -0
  196. package/src/cli/dotenv-store.js +143 -0
  197. package/src/cli/event.js +2281 -0
  198. package/src/cli/fs-selection.js +115 -0
  199. package/src/cli/fs.js +159 -343
  200. package/src/cli/host.js +672 -0
  201. package/src/cli/image.js +4 -6
  202. package/src/cli/index.js +343 -151
  203. package/src/cli/ipfs.js +34 -30
  204. package/src/cli/kickstart.js +1 -1
  205. package/src/cli/kubectl.js +218 -41
  206. package/src/cli/lxd.js +3 -3
  207. package/src/cli/monitor.js +1359 -26
  208. package/src/cli/package.js +90 -0
  209. package/src/cli/release.js +16 -12
  210. package/src/cli/repository.js +586 -363
  211. package/src/cli/run.js +398 -843
  212. package/src/cli/secrets.js +2158 -964
  213. package/src/cli/ssh.js +478 -279
  214. package/src/cli/state.js +394 -0
  215. package/src/cli/static.js +2 -2
  216. package/src/cli/system.js +1 -1
  217. package/src/cli/test.js +203 -125
  218. package/src/cli/vultr.js +61 -21
  219. package/src/cli/wireguard.js +1112 -306
  220. package/src/client/components/core/CalendarCore.js +53 -96
  221. package/src/client/components/core/Docs.js +327 -303
  222. package/src/client/components/core/FileExplorer.js +0 -21
  223. package/src/client/components/core/Modal.js +56 -1
  224. package/src/client/components/core/PanelForm.js +4 -4
  225. package/src/client/components/core/Repository.js +56 -0
  226. package/src/client/components/core/Translate.js +7 -0
  227. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  228. package/src/client/components/core/Wallet.js +0 -11
  229. package/src/client/components/core/Worker.js +7 -1
  230. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  231. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  232. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  233. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  234. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  235. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  236. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  237. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  238. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  239. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  240. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
  241. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  242. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  243. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  244. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  245. package/src/client/components/default/AppShellDefault.js +1 -1
  246. package/src/client/components/default/SettingsDefault.js +2 -2
  247. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  248. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  249. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  250. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  251. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
  252. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  253. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  254. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  255. package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
  256. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  257. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  258. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  259. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  260. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  261. package/src/client/public/default/android-chrome-144x144.png +0 -0
  262. package/src/client/public/default/android-chrome-192x192.png +0 -0
  263. package/src/client/public/default/android-chrome-256x256.png +0 -0
  264. package/src/client/public/default/android-chrome-36x36.png +0 -0
  265. package/src/client/public/default/android-chrome-384x384.png +0 -0
  266. package/src/client/public/default/android-chrome-48x48.png +0 -0
  267. package/src/client/public/default/android-chrome-512x512.png +0 -0
  268. package/src/client/public/default/android-chrome-72x72.png +0 -0
  269. package/src/client/public/default/android-chrome-96x96.png +0 -0
  270. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  271. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  272. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  273. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  274. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  275. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  276. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  277. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  278. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  279. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  280. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  281. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  282. package/src/client/public/default/apple-touch-icon.png +0 -0
  283. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  284. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  285. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  286. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  287. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  288. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  289. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  290. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  291. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  292. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  293. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  294. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  295. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  296. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  297. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  298. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  299. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  300. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  301. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  302. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  303. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  304. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  305. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  306. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  307. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  308. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  309. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  310. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  311. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  312. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  313. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  314. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  315. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  316. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  317. package/src/client/public/default/assets/background/white.jpg +0 -0
  318. package/src/client/public/default/browserconfig.xml +1 -1
  319. package/src/client/public/default/favicon-16x16.png +0 -0
  320. package/src/client/public/default/favicon-32x32.png +0 -0
  321. package/src/client/public/default/favicon-48x48.png +0 -0
  322. package/src/client/public/default/favicon.ico +0 -0
  323. package/src/client/public/default/manifest.webmanifest +5 -5
  324. package/src/client/public/default/mstile-144x144.png +0 -0
  325. package/src/client/public/default/mstile-150x150.png +0 -0
  326. package/src/client/public/default/mstile-310x150.png +0 -0
  327. package/src/client/public/default/mstile-310x310.png +0 -0
  328. package/src/client/public/default/mstile-70x70.png +0 -0
  329. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  330. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  331. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  332. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  333. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  334. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  335. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  336. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  337. package/src/client/services/object-layer/object-layer.management.js +6 -10
  338. package/src/client/services/object-layer/object-layer.service.js +3 -1
  339. package/src/client/ssr/RootDocument.js +1 -1
  340. package/src/client/ssr/body/404.js +1 -1
  341. package/src/client/ssr/body/500.js +1 -1
  342. package/src/client/ssr/body/CacheControl.js +1 -1
  343. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  344. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  345. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  346. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  347. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  348. package/src/client/ssr/head/Css.js +1 -1
  349. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  350. package/src/client/ssr/head/DefaultScripts.js +1 -1
  351. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  352. package/src/client/ssr/head/Microdata.js +1 -1
  353. package/src/client/ssr/head/Production.js +1 -1
  354. package/src/client/ssr/head/Pwa.js +1 -1
  355. package/src/client/ssr/head/PwaItemledger.js +1 -1
  356. package/src/client/ssr/head/Seo.js +1 -1
  357. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  358. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  359. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  360. package/src/client/ssr/views/Cyberia404.js +7 -5
  361. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  362. package/src/client/ssr/views/Maintenance.js +1 -1
  363. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  364. package/src/client/ssr/views/Test.js +1 -1
  365. package/src/client/sw/core.sw.js +20 -14
  366. package/src/client-builder/client-build-docs.js +92 -77
  367. package/src/client-builder/client-build-live.js +2 -2
  368. package/src/client-builder/client-build.js +105 -74
  369. package/src/client-builder/client-bundle.js +347 -0
  370. package/src/client-builder/client-dev-server.js +3 -3
  371. package/src/client-builder/client-icons.js +2 -2
  372. package/src/client-builder/ssr.js +3 -3
  373. package/src/client.build.js +3 -6
  374. package/src/client.dev.js +3 -6
  375. package/src/db/DataBaseProvider.js +186 -3
  376. package/src/db/mariadb/MariaDB.js +3 -2
  377. package/src/db/mongo/MongoBootstrap.js +273 -101
  378. package/src/db/mongo/MongoExpress.js +187 -0
  379. package/src/db/mongo/MongooseDB.js +7 -8
  380. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  381. package/src/grpc/cyberia/grpc-server.js +6 -8
  382. package/src/index.js +79 -23
  383. package/src/mailer/EmailRender.js +2 -2
  384. package/src/mailer/MailerInterceptor.js +87 -0
  385. package/src/mailer/MailerProvider.js +50 -6
  386. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
  387. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  388. package/src/projects/cyberia/besu-genesis-generator.js +8 -17
  389. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  390. package/src/projects/cyberia/catalog-cyberia.js +54 -10
  391. package/src/projects/cyberia/gemini-client.js +4 -4
  392. package/src/projects/cyberia/generate-saga.js +5 -12
  393. package/src/projects/cyberia/hot-reload-trigger.js +18 -29
  394. package/src/projects/cyberia/instance-backup.js +210 -0
  395. package/src/projects/cyberia/instance-data.js +178 -258
  396. package/src/projects/cyberia/ipfs-client.js +4 -4
  397. package/src/projects/cyberia/map-preview-generator.js +33 -69
  398. package/src/projects/cyberia/object-layer.js +153 -320
  399. package/src/projects/cyberia/seed-audio.js +139 -0
  400. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  401. package/src/projects/cyberia/semantic-layer-generator.js +3 -19
  402. package/src/projects/cyberia/stat-balance.js +81 -0
  403. package/src/projects/cyberia/stat-commands.js +23 -0
  404. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  405. package/src/projects/underpost/catalog-underpost.js +9 -1
  406. package/src/proxy.js +4 -7
  407. package/src/runtime/cyberia-client/Dockerfile +17 -8
  408. package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
  409. package/src/runtime/cyberia-server/Dockerfile +18 -6
  410. package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
  411. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  412. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  413. package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
  414. package/src/runtime/engine-cyberia/compose.env +17 -5
  415. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  416. package/src/runtime/express/Express.js +32 -27
  417. package/src/runtime/lampp/Lampp.js +22 -70
  418. package/src/runtime/nginx/Nginx.js +1 -1
  419. package/src/runtime/wp/Wp.js +16 -48
  420. package/src/server/{catalog.js → build/catalog.js} +15 -5
  421. package/src/server/build/coverage.js +188 -0
  422. package/src/server/build/execution.js +402 -0
  423. package/src/server/build/package.js +508 -0
  424. package/src/server/build/testing.js +578 -0
  425. package/src/server/{dns.js → network/dns.js} +144 -11
  426. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  427. package/src/server/{middlewares.js → network/middlewares.js} +11 -5
  428. package/src/server/network/node-capability.js +98 -0
  429. package/src/server/{peer.js → network/peer.js} +3 -3
  430. package/src/server/{proxy.js → network/proxy.js} +7 -13
  431. package/src/server/network/router.js +356 -0
  432. package/src/server/{tls.js → network/tls.js} +2 -2
  433. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  434. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  435. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  436. package/src/server/{cri.js → ops/cri.js} +2 -2
  437. package/src/server/{cron.js → ops/cron.js} +349 -74
  438. package/src/server/ops/event-notification.js +284 -0
  439. package/src/server/{logger.js → ops/logger.js} +143 -23
  440. package/src/server/ops/monitoring.js +1724 -0
  441. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  442. package/src/server/{conf.js → runtime/conf.js} +516 -423
  443. package/src/server/runtime/config-scope.js +215 -0
  444. package/src/server/runtime/environment.js +149 -0
  445. package/src/server/{process.js → runtime/process.js} +153 -12
  446. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  447. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  448. package/src/server/{start.js → runtime/start.js} +128 -39
  449. package/src/server/{auth.js → security/auth.js} +7 -8
  450. package/src/server/security/container-storage.js +382 -0
  451. package/src/server/{crypto.js → security/crypto.js} +1 -1
  452. package/src/server/{selinux.js → security/selinux.js} +1 -1
  453. package/src/server/{backup.js → storage/backup.js} +17 -8
  454. package/src/server/{data-query.js → storage/data-query.js} +17 -1
  455. package/src/server/storage/downloader.js +121 -0
  456. package/src/server/storage/repository.js +68 -0
  457. package/src/server/storage/zip.js +153 -0
  458. package/src/server.js +4 -7
  459. package/src/ws/IoInterface.js +1 -1
  460. package/src/ws/IoServer.js +1 -1
  461. package/src/ws/core/core.ws.connection.js +1 -1
  462. package/src/ws/core/core.ws.emit.js +1 -1
  463. package/src/ws/core/core.ws.server.js +1 -1
  464. package/src/ws/default/default.ws.connection.js +1 -1
  465. package/src/ws/default/default.ws.emit.js +1 -1
  466. package/src/ws/default/default.ws.server.js +1 -1
  467. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  468. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  469. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  470. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  471. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  472. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  473. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
  474. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  475. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  476. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
  477. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  478. package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
  479. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  480. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  481. package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
  482. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  483. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  484. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  485. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  486. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  487. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  488. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  489. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  490. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  491. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  492. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  493. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  494. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  495. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  496. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  497. package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
  498. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
  499. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  500. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  501. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  502. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  503. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  504. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  505. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  506. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  507. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  508. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  509. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  510. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  511. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  512. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  513. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  514. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  515. package/test/support/shell-harness.js +73 -0
  516. package/test/unit/build-template.test.js +97 -0
  517. package/test/unit/build-workflow-offline.test.js +132 -0
  518. package/test/unit/catalog.test.js +71 -0
  519. package/test/unit/client-build-docs.test.js +361 -0
  520. package/test/unit/client-bundle.test.js +313 -0
  521. package/test/unit/conf-loading.test.js +303 -0
  522. package/test/unit/conf-resolution.test.js +996 -0
  523. package/test/unit/coverage-artifact.test.js +221 -0
  524. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  525. package/test/unit/cyberia/instance-backup.test.js +183 -0
  526. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  527. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  528. package/test/unit/cyberia/stat-balance.test.js +69 -0
  529. package/test/unit/deploy-env-resolution.test.js +38 -0
  530. package/test/unit/deploy-log-table.test.js +518 -0
  531. package/test/unit/downloader-integrity.test.js +98 -0
  532. package/test/unit/execution-profiles.test.js +219 -0
  533. package/test/unit/file-reference-registry.test.js +141 -0
  534. package/test/unit/fleet-sync-source.test.js +149 -0
  535. package/test/unit/fs-storage-paths.test.js +596 -0
  536. package/test/unit/logger-redaction.test.js +141 -0
  537. package/test/unit/package.test.js +457 -0
  538. package/test/unit/prepare-host.test.js +382 -0
  539. package/test/unit/process-environment.test.js +18 -0
  540. package/test/unit/propagation-message.test.js +78 -0
  541. package/test/unit/redeploy-plan.test.js +24 -0
  542. package/test/unit/release-bump.test.js +474 -0
  543. package/test/unit/ssh-output-redaction.test.js +28 -0
  544. package/test/unit/start-options.test.js +46 -0
  545. package/test/unit/test-tiers.test.js +220 -0
  546. package/test/unit/zip-archive.test.js +142 -0
  547. package/vitest.config.js +66 -0
  548. package/.nycrc +0 -9
  549. package/deploy/lib/logging.sh +0 -96
  550. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  551. package/manifests/mongodb/pv-pvc.yaml +0 -59
  552. package/scripts/coverall-test-cyberia.sh +0 -24
  553. package/scripts/coverall-test.sh +0 -24
  554. package/scripts/link-local-underpost-cli.sh +0 -6
  555. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  556. package/src/api/user/user.build.js +0 -16
  557. package/src/cli/env.js +0 -177
  558. package/src/client/components/core/Blockchain.js +0 -41
  559. package/src/client/components/core/Blog.js +0 -9
  560. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  561. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
  562. package/src/client/public/default/assets/background/dark.jpg +0 -0
  563. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  564. package/src/client/public/default/assets/background/white0.jpg +0 -0
  565. package/src/client/public/doc/favicon.ico +0 -0
  566. package/src/client/public/doc/sitemap +0 -148
  567. package/src/server/downloader.js +0 -73
  568. package/src/server/environment.js +0 -98
package/src/cli/deploy.js CHANGED
@@ -5,13 +5,9 @@
5
5
  */
6
6
 
7
7
  import {
8
- buildKindPorts,
9
- buildPortProxyRouter,
10
- buildProxyRouter,
11
8
  clusterTypeFactory,
12
9
  Config,
13
10
  deployHostsFactory,
14
- deployRangePortFactory,
15
11
  gatewayApiEnabledFactory,
16
12
  getDataDeploy,
17
13
  instanceStatusPageEntriesFactory,
@@ -19,16 +15,22 @@ import {
19
15
  loadConfServerJson,
20
16
  loadReplicas,
21
17
  nextTrafficFactory,
22
- pathPortAssignmentFactory,
23
18
  schedulableNodeFactory,
24
19
  trafficFromRoutingInfoFactory,
25
- } from '../server/conf.js';
26
- import { cronDeployIdResolve } from '../server/cron.js';
27
- import { loggerFactory } from '../server/logger.js';
28
- import { HOST_VOLUME_ROOT } from '../server/environment.js';
29
- import { shellExec } from '../server/process.js';
30
- import { runSELinuxCommands, selinuxRestoreconCommandFactory } from '../server/selinux.js';
31
- import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime-status.js';
20
+ } from '../server/runtime/conf.js';
21
+ import {
22
+ buildKindPorts,
23
+ buildPortProxyRouter,
24
+ buildProxyRouter,
25
+ deployRangePortFactory,
26
+ pathPortAssignmentFactory,
27
+ readDeployRoutes,
28
+ } from '../server/network/router.js';
29
+ import { loggerFactory } from '../server/ops/logger.js';
30
+ import { HOST_VOLUME_ROOT } from '../server/runtime/environment.js';
31
+ import { shellExec } from '../server/runtime/process.js';
32
+ import { ensureContainerStorage, ensureContainerStorageSubtree } from '../server/security/container-storage.js';
33
+ import { INTERNAL_READY_PATH, INTERNAL_HEALTH_PATH } from '../server/runtime/runtime-status.js';
32
34
  import { staticContextRoutesFactory, statusPageRoutesFactory } from '../client-builder/client-build.js';
33
35
  import {
34
36
  UNDERPOST_GATEWAY,
@@ -41,13 +43,15 @@ import {
41
43
  syncStaticAssetFromPod,
42
44
  writeHostServerConf,
43
45
  writeStaticAsset,
44
- } from '../server/underpost-gateway.js';
46
+ } from '../server/network/underpost-gateway.js';
45
47
  import { getCapVariableName } from '../client/components/core/CommonJs.js';
46
48
  import fs from 'fs-extra';
47
49
  import nodePath from 'node:path';
48
50
  import dotenv from 'dotenv';
49
51
  import os from 'node:os';
50
52
  import crypto from 'node:crypto';
53
+ import { appSecretName } from './app.js';
54
+ import { domainContextFactory } from './domains.js';
51
55
  import Underpost from '../index.js';
52
56
 
53
57
  /**
@@ -79,7 +83,7 @@ const GATEWAY_CONTROLLER_NAME = `${GATEWAY_EXTENSION_GROUP}/gatewayclass-control
79
83
  // Gateway references: one name, resolved through gatewayApiConfigFactory, so an
80
84
  // override reaches the installer and the manifests together.
81
85
  const GATEWAY_CLASS_DEFAULT = 'eg';
82
- // Where `bin client <deployId> <env>` writes each host's bundle, including the
86
+ // Where `bin client <deployId> --env <env>` writes each host's bundle, including the
83
87
  // SSR status views declared in conf.ssr.json (`<host><path>/<status>/index.html`).
84
88
  // Engine root inside the workload container; the built PWA artifacts live under
85
89
  // its `public/` tree, which is where the static edge documents are sourced from.
@@ -89,6 +93,17 @@ const UPSTREAM_FAILURE_STATUSES = [502, 503, 504];
89
93
 
90
94
  const CONTAINER_ENGINE_ROOT = '/home/dd/engine';
91
95
 
96
+ // The manifest set `deploy --build-manifest` produces for one environment.
97
+ const DEFAULT_MANIFEST_FILES = [
98
+ 'deployment.yaml',
99
+ 'proxy.yaml',
100
+ 'gateway.yaml',
101
+ 'httproute.yaml',
102
+ 'pv-pvc.yaml',
103
+ 'traffic-service.yaml',
104
+ 'grpc-service.yaml',
105
+ ];
106
+
92
107
  /**
93
108
  * Maps a host/path's edge-served views onto the statuses the gateway intercepts
94
109
  * for it, and the context directory each status is answered from.
@@ -132,10 +147,10 @@ const GATEWAY_DURATION_UNITS = [
132
147
  ];
133
148
 
134
149
  /**
135
- * Converts an HTTPProxy duration (`300000ms`, `10s`, `infinity`) into a Gateway
150
+ * Converts an HTTPProxy duration (`10000ms`, `10s`, `infinity`) into a Gateway
136
151
  * API Duration. The Gateway API grammar allows at most 5 digits per component,
137
152
  * so a value that overflows in one unit is re-expressed in a coarser one
138
- * (`300000ms` → `5m`); `infinity` maps to `0s`, which disables the timeout.
153
+ * (`10000ms` → `5m`); `infinity` maps to `0s`, which disables the timeout.
139
154
  * @param {string|number} value - Source duration.
140
155
  * @returns {string|null} Gateway API Duration, or null when unset/unparsable.
141
156
  */
@@ -154,12 +169,12 @@ const gatewayDurationFactory = (value) => {
154
169
 
155
170
  const logger = loggerFactory(import.meta);
156
171
 
157
- // hostPath volume trees are written under the operator's home directory, which
158
- // carries a label no unprivileged container can read. Cluster bring-up registers
159
- // the persistent mapping for HOST_VOLUME_ROOT; this applies it to what a deploy
160
- // just wrote. A no-op where SELinux or its userspace is absent.
161
- const restoreContainerContext = (path) =>
162
- runSELinuxCommands([selinuxRestoreconCommandFactory(path)], { execute: shellExec });
172
+ // hostPath volume trees are written under the operator's home directory, which carries a label no
173
+ // unprivileged container can read. This registers the persistent mapping and restores the tree,
174
+ // rather than only restoring it: a deploy must not depend on cluster bring-up having run on this
175
+ // host, and a plain `restorecon` against an unmapped path re-applies the very default that makes
176
+ // the volume unreadable. A no-op where SELinux or its userspace is absent.
177
+ const prepareContainerStorage = (path) => ensureContainerStorageSubtree(path, { execute: shellExec });
163
178
 
164
179
  /**
165
180
  * @class UnderpostDeploy
@@ -449,20 +464,15 @@ EOF
449
464
  }) {
450
465
  if (!readinessProbe) throw new Error(`Refusing to build ${deployId}-${env}-${suffix} without a readiness probe`);
451
466
  if (!cmd)
452
- cmd =
453
- pullBundle || skipFullBuild
454
- ? [
455
- // When pullBundle (or skipFullBuild) is set the container pulls the pre-built client
456
- // bundle from Cloudinary (push-bundle must have been run on the dev machine beforehand).
457
- `underpost secret underpost --create-from-env`,
458
- `underpost start --build --run --pull-bundle --skip-full-build ${deployId} ${env}`,
459
- ]
460
- : [
461
- // `npm install -g npm@11.2.0`,
462
- // `npm install -g underpost`,
463
- `underpost secret underpost --create-from-env`,
464
- `underpost start --build --run ${deployId} ${env}`,
465
- ];
467
+ // One command, and deliberately only `start`. This string is executed by whatever
468
+ // underpost the image happens to ship, which is as new as the last npm publish and no
469
+ // newer — so it may name only CLI surface that has always existed. Everything the
470
+ // deployment needs beyond that, host configuration included, is done inside the start
471
+ // lifecycle, where the code is the freshly pulled source rather than the image snapshot.
472
+ // `--pull-bundle` fetches the pre-built client bundle instead of building it in-pod.
473
+ cmd = [
474
+ `underpost start --build --run${pullBundle || skipFullBuild ? ' --pull-bundle --skip-full-build' : ''} ${deployId} ${env}`,
475
+ ];
466
476
  const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
467
477
  if (!volumes) volumes = [];
468
478
  const confVolume = fs.existsSync(`./engine-private/conf/${deployId}/conf.volume.json`)
@@ -503,6 +513,12 @@ ${
503
513
  envFrom:
504
514
  - secretRef:
505
515
  name: underpost-config
516
+ # The deployment's own environment, with its .env.<env>.oci overlay already
517
+ # applied by 'app apply'. Optional so a deployment predating that Secret still
518
+ # starts; the in-pod 'app load' resolves the same overlay as a second path.
519
+ - secretRef:
520
+ name: ${appSecretName(deployId, env)}
521
+ optional: true
506
522
  ${
507
523
  internalStatusPort
508
524
  ? ` env:
@@ -601,7 +617,7 @@ spec:
601
617
  * @param {string} options.namespace - Kubernetes namespace for the deployment (defaults to "default").
602
618
  * @param {string} [options.versions] - Comma-separated list of versions to deploy.
603
619
  * @param {string} [options.cmd] - Custom initialization command for deploymentYamlPartsFactory (comma-separated commands).
604
- * @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "300000ms", "infinity").
620
+ * @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "10000ms", "infinity").
605
621
  * @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
606
622
  * @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
607
623
  * @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
@@ -1096,6 +1112,41 @@ spec:
1096
1112
  kind: ClusterIssuer
1097
1113
  secretName: ${host}`;
1098
1114
  },
1115
+ /**
1116
+ * Mirrors each deploy's built development manifests into the project tree, and refreshes the
1117
+ * public default configuration they belong to.
1118
+ *
1119
+ * The project repository carries the development manifests of every routed deploy; the
1120
+ * private build directory is where they are produced. A manifest the build stopped producing
1121
+ * is removed here rather than left behind, so the two cannot disagree about what a deploy
1122
+ * ships.
1123
+ * @param {string[]} [deployIds] - Deploy ids to mirror; the route table plus `dd-cron` otherwise.
1124
+ * @returns {Array<{deployId: string, copied: string[], removed: string[]}>} What was mirrored.
1125
+ * @memberof UnderpostDeploy
1126
+ */
1127
+ mirrorDefaultManifests(deployIds = readDeployRoutes().concat(['dd-cron'])) {
1128
+ const mirrored = [];
1129
+ for (const deployId of deployIds) {
1130
+ const copied = [];
1131
+ const removed = [];
1132
+ for (const file of DEFAULT_MANIFEST_FILES) {
1133
+ const source = `./engine-private/conf/${deployId}/build/development/${file}`;
1134
+ const target = `./manifests/deployment/${deployId}-development/${file}`;
1135
+ if (fs.existsSync(source)) {
1136
+ fs.copySync(source, target);
1137
+ copied.push(file);
1138
+ } else if (fs.existsSync(target)) {
1139
+ fs.removeSync(target);
1140
+ removed.push(file);
1141
+ }
1142
+ }
1143
+ shellExec(`node bin new --dev --default-conf --deploy-id ${deployId}`);
1144
+ mirrored.push({ deployId, copied, removed });
1145
+ }
1146
+ logger.info('Default deployment manifests mirrored', { deployIds: mirrored.map(({ deployId }) => deployId) });
1147
+ return mirrored;
1148
+ },
1149
+
1099
1150
  /**
1100
1151
  * Retrieves the current traffic status for a deployment.
1101
1152
  * @param {string} deployId - Deployment ID for which the traffic status is being retrieved.
@@ -1735,6 +1786,45 @@ ${rules}`;
1735
1786
  return `${ready}`.includes('true');
1736
1787
  },
1737
1788
 
1789
+ /**
1790
+ * Reports whether a Deployment object exists at all.
1791
+ *
1792
+ * The inactive colour of a blue/green pair is routinely absent, so callers
1793
+ * that act on it must distinguish "not deployed yet" from a failed lookup.
1794
+ * @param {string} deployment - Deployment name.
1795
+ * @param {string} [namespace] - Kubernetes namespace.
1796
+ * @returns {boolean} True when the object is present.
1797
+ */
1798
+ deploymentExists({ deployment, namespace = 'default' }) {
1799
+ return !!`${
1800
+ shellExec(`kubectl get deployment ${deployment} -n ${namespace} --ignore-not-found -o name`, {
1801
+ stdout: true,
1802
+ silent: true,
1803
+ silentOnError: true,
1804
+ }) || ''
1805
+ }`.trim();
1806
+ },
1807
+
1808
+ /**
1809
+ * Node a live Deployment is pinned to, read from its `nodeSelector`.
1810
+ *
1811
+ * A colour created next to a live one must inherit that placement: its
1812
+ * hostPath volumes exist on that node only.
1813
+ * @param {string} deployment - Deployment name.
1814
+ * @param {string} [namespace] - Kubernetes namespace.
1815
+ * @returns {string} Node hostname, or empty when the workload is unpinned or absent.
1816
+ */
1817
+ deploymentNode({ deployment, namespace = 'default' }) {
1818
+ if (!deployment) return '';
1819
+ return `${
1820
+ shellExec(
1821
+ `kubectl get deployment ${deployment} -n ${namespace} ` +
1822
+ `-o jsonpath='{.spec.template.spec.nodeSelector.kubernetes\.io/hostname}'`,
1823
+ { stdout: true, silent: true, silentOnError: true },
1824
+ ) || ''
1825
+ }`.trim();
1826
+ },
1827
+
1738
1828
  /**
1739
1829
  * Reports whether the Deployment controller has observed the current
1740
1830
  * generation and every desired replica is updated, Ready, and Available.
@@ -1890,12 +1980,11 @@ ${rules}`;
1890
1980
  ? 'project'
1891
1981
  : null,
1892
1982
  });
1893
- // The documents were just written under the operator's home tree, whose
1894
- // policy label (user_home_t) the unprivileged gateway container cannot
1895
- // read it would answer 403 for every one of them. The persistent
1896
- // mapping is registered at cluster bring-up; restore it here so files
1897
- // this pass created carry it too.
1898
- restoreContainerContext(hostRoot);
1983
+ // The documents were just written under the operator's home tree, whose policy label
1984
+ // (user_home_t) the unprivileged gateway container cannot read — it would answer 403 for
1985
+ // every one of them. Register the mapping for the volume root and restore this subtree, so
1986
+ // the files this pass created carry the shared container label.
1987
+ prepareContainerStorage(hostRoot);
1899
1988
  logger.info('Static edge documents placed', {
1900
1989
  deployId,
1901
1990
  podName: podName || '(no running workload; placed from this checkout)',
@@ -2096,7 +2185,7 @@ ${rules}`;
2096
2185
  * @param {boolean} options.disableUpdateVolume - Whether to disable volume updates.
2097
2186
  * @param {boolean} options.disableUpdateUnderpostConfig - Whether to disable Underpost config updates.
2098
2187
  * @param {string} [options.namespace] - Kubernetes namespace for the deployment (defaults to "default").
2099
- * @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "300000ms", "infinity").
2188
+ * @param {string} [options.timeoutResponse] - HTTPProxy per-route response timeout (e.g. "10000ms", "infinity").
2100
2189
  * @param {string} [options.timeoutIdle] - HTTPProxy per-route idle timeout (e.g. "10s", "infinity").
2101
2190
  * @param {string} [options.retryCount] - HTTPProxy per-route retry count (e.g. 3).
2102
2191
  * @param {string} [options.retryPerTryTimeout] - HTTPProxy per-route per-try timeout (e.g. "150ms").
@@ -2157,8 +2246,7 @@ ${Underpost.deploy.buildCertManagerCertificate({ host, namespace })}
2157
2246
  EOF`);
2158
2247
  }
2159
2248
  return;
2160
- } else if (!deployList || deployList === 'dd')
2161
- deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
2249
+ } else if (!deployList || deployList === 'dd') deployList = readDeployRoutes().join(',');
2162
2250
  const deployIds = deployList
2163
2251
  .split(',')
2164
2252
  .map((id) => id.trim())
@@ -2202,11 +2290,19 @@ EOF`);
2202
2290
  Underpost.deploy.syncStaticAssets(deployId, env, options);
2203
2291
  return;
2204
2292
  }
2293
+ // Before the `--build-manifest` return: a manifest build is when the Secrets it mounts
2294
+ // must exist, and `run sync` reaches this command with `--build-manifest --info-router`.
2295
+ if (!options.disableUpdateUnderpostConfig) {
2296
+ Underpost.host.apply(domainContextFactory({ env, namespace }));
2297
+ // Each deployment's own env, OCI overlay applied. Without it the overlay reaches a pod
2298
+ // only through in-pod `app load`, so a stale image keeps the base file's 127.0.0.1.
2299
+ for (const _deployId of deployIds)
2300
+ Underpost.app.apply(domainContextFactory({ env, namespace, args: { 'deploy-id': _deployId } }));
2301
+ }
2205
2302
  if (options.infoRouter === true || options.buildManifest === true) {
2206
2303
  logger.info('router', await Underpost.deploy.routerFactory(deployList, env));
2207
2304
  return;
2208
2305
  }
2209
- if (!options.disableUpdateUnderpostConfig) Underpost.deploy.configMap(env);
2210
2306
 
2211
2307
  for (const _deployId of deployList.split(',')) {
2212
2308
  const deployId = _deployId.trim();
@@ -2392,27 +2488,6 @@ EOF`);
2392
2488
  }
2393
2489
  }
2394
2490
  },
2395
- /**
2396
- * Creates a Kubernetes Secret for a deployment (replaces configMap for secret data).
2397
- * Secrets are mounted as tmpfs (never written to node disk) and support RBAC restrictions.
2398
- * @param {string} env - Environment for which the secret is being created.
2399
- * @param {string} [namespace='default'] - Kubernetes namespace for the secret.
2400
- * @memberof UnderpostDeploy
2401
- */
2402
- configMap(env, namespace = 'default') {
2403
- const cronDeployId = cronDeployIdResolve() || 'dd-cron';
2404
- const envFilePath = `/home/dd/engine/engine-private/conf/${cronDeployId}/.env.${env}`;
2405
- // `--from-env-file` turns every KEY=VALUE into a secret key that the Deployment injects via
2406
- // `envFrom`. Strip shell/runtime-critical keys (notably PATH) first — an injected PATH
2407
- // overrides the image's own and breaks coreutils/sudo resolution inside the pod.
2408
- const sanitizedEnvPath = `${envFilePath}.secret`;
2409
- fs.writeFileSync(sanitizedEnvPath, Underpost.secret.sanitizeSecretEnvFile(fs.readFileSync(envFilePath, 'utf8')));
2410
- shellExec(`kubectl delete secret underpost-config -n ${namespace} --ignore-not-found`);
2411
- shellExec(
2412
- `kubectl create secret generic underpost-config --from-env-file=${sanitizedEnvPath} --dry-run=client -o yaml | kubectl apply -f - -n ${namespace}`,
2413
- );
2414
- fs.removeSync(sanitizedEnvPath);
2415
- },
2416
2491
  /**
2417
2492
  * Switches the traffic for a deployment.
2418
2493
  *
@@ -2552,7 +2627,7 @@ EOF`);
2552
2627
  const grpcServicePath = `./engine-private/conf/${deployId}/build/${env}/grpc-service.yaml`;
2553
2628
  if (fs.existsSync(grpcServicePath)) shellExec(`kubectl apply -f ${grpcServicePath} -n ${namespace}`);
2554
2629
 
2555
- Underpost.env.set(`${deployId}-${env}-traffic`, targetTraffic);
2630
+ Underpost.host.store.set(`${deployId}-${env}-traffic`, targetTraffic);
2556
2631
  },
2557
2632
 
2558
2633
  /**
@@ -2562,9 +2637,8 @@ EOF`);
2562
2637
  * behaves identically everywhere:
2563
2638
  *
2564
2639
  * 1. **Explicit node** — `node` (the resolved `--node` value). Upstream
2565
- * runners derive it from the comma-path field or `--node-name`
2566
- * (`run sync`: `path.split(',')[4]` > `--node-name` > default) and from
2567
- * `--node-name` directly (`run instance`).
2640
+ * runners derive it from `--node-name` (`run sync`: `--node-name` >
2641
+ * cluster-type default; `run instance`: `--node-name` directly).
2568
2642
  * 2. **`UNDERPOST_DEPLOY_NODE` env** — for kubeadm / k3s, the configured
2569
2643
  * target node name. This makes hostPath PV `nodeAffinity` deterministic
2570
2644
  * regardless of where the manifest is *built*: building inside a
@@ -2668,10 +2742,15 @@ EOF`);
2668
2742
  const localHost = os.hostname();
2669
2743
  dataNode = options.nodeName || localHost;
2670
2744
  if (dataNode === localHost) {
2671
- // Target node is the control plane / current host: write directly.
2672
- if (!fs.existsSync(rootVolumeHostPath)) fs.mkdirSync(rootVolumeHostPath, { recursive: true });
2745
+ // Target node is the control plane / current host: write directly. The directory is
2746
+ // created owned by the invoking user — the root above it may be root-owned, and the copy
2747
+ // that follows is unprivileged — and labeled before as well as after the copy, so the
2748
+ // files land already carrying the shared container label instead of inheriting the
2749
+ // operator home tree's.
2750
+ const { uid, gid } = os.userInfo();
2751
+ ensureContainerStorage(rootVolumeHostPath, { execute: shellExec, owner: `${uid}:${gid}` });
2673
2752
  fs.copySync(volume.volumeMountPath, rootVolumeHostPath);
2674
- restoreContainerContext(rootVolumeHostPath);
2753
+ prepareContainerStorage(rootVolumeHostPath);
2675
2754
  } else {
2676
2755
  // Target node is remote: fs.copySync would only write the control-plane
2677
2756
  // filesystem, leaving the real node's hostPath empty. Ship the folder to
@@ -2691,6 +2770,9 @@ EOF`);
2691
2770
  host: nodeHost,
2692
2771
  localDir: volume.volumeMountPath,
2693
2772
  remoteDir: rootVolumeHostPath,
2773
+ // The volume is mounted on that node, so the label has to exist there. Labeling the
2774
+ // control plane's copy of the path would leave the pod reading an unlabeled tree.
2775
+ containerStorage: true,
2694
2776
  ...(options.sshKeyPath ? { keyPath: options.sshKeyPath } : {}),
2695
2777
  });
2696
2778
  }
@@ -10,8 +10,8 @@
10
10
 
11
11
  import fs from 'fs-extra';
12
12
  import nodePath from 'path';
13
- import { getRootDirectory, shellExec } from '../server/process.js';
14
- import { loggerFactory } from '../server/logger.js';
13
+ import { getRootDirectory, shellExec } from '../server/runtime/process.js';
14
+ import { loggerFactory } from '../server/ops/logger.js';
15
15
  import Nginx from '../runtime/nginx/Nginx.js';
16
16
 
17
17
  const logger = loggerFactory(import.meta);
@@ -143,7 +143,7 @@ class UnderpostDockerCompose {
143
143
  // Point the template .env.example at the Docker service-discovery hosts
144
144
  // before `new engine` copies it. The generated dd-engine/.env.development
145
145
  // is seeded from this file and is applied over the process env by
146
- // loadConf (src/server/conf.js), so without this the engine would fall
146
+ // loadConf (src/server/runtime/conf.js), so without this the engine would fall
147
147
  // back to the .env.example localhost defaults for DB_HOST/VALKEY_HOST.
148
148
  'sed -i "s#^DB_HOST=.*#DB_HOST=$${DB_HOST}#" .env.example',
149
149
  'sed -i "s#^VALKEY_HOST=.*#VALKEY_HOST=$${VALKEY_HOST}#" .env.example',
@@ -151,7 +151,7 @@ class UnderpostDockerCompose {
151
151
  'cd /home/dd',
152
152
  'underpost new engine',
153
153
  ];
154
- return ['underpost secret underpost --create-from-env', `underpost start --build --run ${deployId} ${env}`];
154
+ return [`underpost start --build --run ${deployId} ${env}`];
155
155
  }
156
156
 
157
157
  /**
@@ -192,7 +192,7 @@ services:
192
192
  MONGO_IMAGE=mongo:latest
193
193
  VALKEY_IMAGE=valkey/valkey:latest
194
194
  APP_IMAGE=underpost/underpost-engine
195
- APP_TAG=v3.3.0
195
+ APP_TAG=v3.3.76
196
196
  PROXY_IMAGE=nginx:stable-alpine
197
197
  PROMETHEUS_IMAGE=prom/prometheus:latest
198
198
  GRAFANA_IMAGE=grafana/grafana:latest
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Shared command surface for the four domains.
3
+ *
4
+ * `secret`, `host`, `app` and `state` are registered from one factory, so the action list and the
5
+ * option list exist exactly once. Symmetry is not a convention the four commands agree to follow
6
+ * — it is structural: a verb or a flag added here appears on all four, and one that is not here
7
+ * cannot appear on any of them.
8
+ *
9
+ * The three configuration domains carry a durable source; `state` is the runtime monitoring /
10
+ * telemetry layer and carries none, so its actions read and export a live observation instead.
11
+ * The verbs are the same either way — see {@link UnderpostState} for what each means there.
12
+ * @module src/cli/domains.js
13
+ * @namespace UnderpostDomains
14
+ */
15
+
16
+ /**
17
+ * The canonical action set. Every domain implements all of these with the same meaning:
18
+ *
19
+ * | action | direction |
20
+ * |---------|-----------------------------------|
21
+ * | setup | onboard the domain, idempotently |
22
+ * | load | durable store -> local runtime |
23
+ * | publish | local runtime -> durable store |
24
+ * | apply | durable store -> live cluster |
25
+ * | status | read-only report |
26
+ * | rotate | replace the current projection |
27
+ * | clean | withdraw local traces |
28
+ * @constant {Array<{name: string, summary: string}>}
29
+ * @memberof UnderpostDomains
30
+ */
31
+ const DOMAIN_ACTIONS = [
32
+ { name: 'setup', summary: 'Onboards the domain: provisions whatever it needs, then converges it. Idempotent.' },
33
+ { name: 'load', summary: 'Loads the durable source into the local runtime environment.' },
34
+ { name: 'publish', summary: 'Writes the local runtime environment into the durable source.' },
35
+ { name: 'apply', summary: 'Projects the durable source into the live cluster.' },
36
+ { name: 'status', summary: 'Read-only report of the domain: sources, keys, and drift from the cluster.' },
37
+ { name: 'rotate', summary: 'Replaces the current projection or encryption identity.' },
38
+ { name: 'clean', summary: 'Withdraws the domain traces from the local filesystem.' },
39
+ ];
40
+
41
+ /**
42
+ * The canonical option set, identical on every action of every domain.
43
+ *
44
+ * Deliberately five. Everything a single domain used to carry its own flag for — an Age
45
+ * recipient, a secret name list, a sub-configuration — passes through `--args` instead, so the
46
+ * visible surface does not grow when one domain gains a parameter.
47
+ * @constant {Array<{flags: string, description: string}>}
48
+ * @memberof UnderpostDomains
49
+ */
50
+ const DOMAIN_OPTIONS = [
51
+ { flags: '--env <env>', description: 'Target environment: development | production | test (default: production).' },
52
+ { flags: '--namespace <namespace>', description: 'Kubernetes namespace to act on (default: default).' },
53
+ {
54
+ flags: '--args <key=value-list>',
55
+ description:
56
+ 'Comma-separated domain parameters, e.g. `names=postgres-secret`, `recipient=age1...`, `sub-conf=nexodev`.',
57
+ },
58
+ { flags: '--dry-run', description: 'Reports what the action would change without changing anything.' },
59
+ { flags: '--force', description: 'Confirms the irreversible variant of the action.' },
60
+ ];
61
+
62
+ /**
63
+ * Key-level operators a domain may register alongside the canonical seven.
64
+ *
65
+ * They are not part of {@link DOMAIN_ACTIONS} on purpose: the canonical set is a bulk-lifecycle
66
+ * verb set, symmetric across every domain, with no place for per-key reads and writes. A domain
67
+ * that owns a key-value store opts into these instead of getting a command of its own — the host
68
+ * configuration store is the host domain's, so `underpost host get KEY` is where it is read.
69
+ * @constant {Array<string>}
70
+ * @memberof UnderpostDomains
71
+ */
72
+ const DOMAIN_STORE_OPERATORS = ['get', 'set', 'delete', 'list'];
73
+
74
+ const DOMAIN_STORE_OPTIONS = [
75
+ { flags: '--plain', description: 'Prints the value in plain text (get).' },
76
+ { flags: '--filter <keyword>', description: 'Filters by matching key or value (list).' },
77
+ { flags: '--copy', description: 'Copies the value to the clipboard (get).' },
78
+ ];
79
+
80
+ /**
81
+ * Parses `--args key=value,key=value` into an object.
82
+ *
83
+ * Values may contain `=` (an Age recipient does not, a password can), so only the first
84
+ * separator splits. A bare `key` is the boolean shorthand for `key=true`.
85
+ * @param {string} [value] - Raw `--args` string.
86
+ * @returns {Object<string, string|boolean>} Parsed parameters.
87
+ * @memberof UnderpostDomains
88
+ */
89
+ const parseDomainArgs = (value) => {
90
+ // Idempotent: an already-parsed object passes through, so normalizing a context twice — once
91
+ // at the CLI boundary, once inside the action a programmatic caller invoked directly — is safe.
92
+ if (value && typeof value === 'object') return { ...value };
93
+ return Object.fromEntries(
94
+ `${value ?? ''}`
95
+ .split(',')
96
+ .map((entry) => entry.trim())
97
+ .filter(Boolean)
98
+ .map((entry) => {
99
+ const index = entry.indexOf('=');
100
+ return index === -1 ? [entry, true] : [entry.slice(0, index).trim(), entry.slice(index + 1)];
101
+ }),
102
+ );
103
+ };
104
+
105
+ /**
106
+ * Normalizes the parsed options handed to a domain action, so every domain receives the same
107
+ * shape and none of them re-derives a default of its own.
108
+ * @param {object} [options] - Commander options for the invocation.
109
+ * @returns {{env: string, namespace: string, args: object, dryRun: boolean, force: boolean}} Normalized context.
110
+ * @memberof UnderpostDomains
111
+ */
112
+ const domainContextFactory = (options = {}) => ({
113
+ env: `${options.env ?? ''}`.trim() || 'production',
114
+ namespace: `${options.namespace ?? ''}`.trim() || 'default',
115
+ args: parseDomainArgs(options.args),
116
+ dryRun: options.dryRun === true,
117
+ force: options.force === true,
118
+ });
119
+
120
+ /**
121
+ * Registers one domain as a top-level command carrying the full canonical surface.
122
+ *
123
+ * The action is a required argument rather than a flag so the three commands read as
124
+ * `underpost <domain> <action>`, and an unknown action fails against the shared list instead of
125
+ * silently doing nothing.
126
+ * @param {object} program - The commander program.
127
+ * @param {object} domain - Domain registration.
128
+ * @param {string} domain.name - Command name (`secret`, `host`, `app`, `state`).
129
+ * @param {boolean} [domain.store] - Whether this domain owns a key-value store, and so carries
130
+ * the key-level operators alongside the canonical actions.
131
+ * @param {string} domain.description - One-line command description.
132
+ * @param {Function} domain.api - Returns the domain API implementing every canonical action.
133
+ * @returns {object} The registered commander command.
134
+ * @memberof UnderpostDomains
135
+ */
136
+ const registerDomainCommand = (program, { name, description, api, store = false }) => {
137
+ const actions = DOMAIN_ACTIONS.map((action) => action.name);
138
+ const operators = store ? DOMAIN_STORE_OPERATORS : [];
139
+ const command = program
140
+ .command(name)
141
+ .argument(
142
+ '<action>',
143
+ `Action to run. One of: ${[...actions, ...operators].join(', ')}.\n` +
144
+ DOMAIN_ACTIONS.map((action) => ` ${action.name.padEnd(8)} ${action.summary}`).join('\n') +
145
+ (store ? `\n ${'get|set|delete|list'.padEnd(8)} Key-level access to the store this domain owns.` : ''),
146
+ )
147
+ .description(description);
148
+ // Only a store-owning domain takes a key and a value; the others keep the bare action surface.
149
+ if (store) {
150
+ command.argument('[key]', 'Key to act on, for the key-level operators.');
151
+ command.argument('[value]', 'Value to write, for the `set` operator.');
152
+ }
153
+ // Commander hands the action `(...arguments, options, command)`, so the options object is
154
+ // second from the end no matter how many positionals this domain declared.
155
+ command.action((...received) => {
156
+ const options = received[received.length - 2];
157
+ const [action, key, value] = received;
158
+ if (operators.includes(action)) {
159
+ if (action !== 'list' && !key) throw new Error(`${name} ${action} requires a key`);
160
+ if (action === 'set' && value === undefined) throw new Error(`${name} set requires a value`);
161
+ return api()[action](key, value, options);
162
+ }
163
+ if (!actions.includes(action))
164
+ throw new Error(`Unknown ${name} action: ${action} (expected ${[...actions, ...operators].join(', ')})`);
165
+ const implementation = api()[action];
166
+ // Guards the contract at the boundary: a domain that has not implemented the full
167
+ // canonical set fails here by name, rather than as `undefined is not a function`.
168
+ if (typeof implementation !== 'function')
169
+ throw new Error(`Domain '${name}' does not implement the '${action}' action`);
170
+ return implementation(domainContextFactory(options));
171
+ });
172
+ for (const option of DOMAIN_OPTIONS) command.option(option.flags, option.description);
173
+ if (store) for (const option of DOMAIN_STORE_OPTIONS) command.option(option.flags, option.description);
174
+ return command;
175
+ };
176
+
177
+ export {
178
+ DOMAIN_ACTIONS,
179
+ DOMAIN_OPTIONS,
180
+ DOMAIN_STORE_OPERATORS,
181
+ domainContextFactory,
182
+ parseDomainArgs,
183
+ registerDomainCommand,
184
+ };