@adonis-agora/durable 1.0.0-rc.2 → 1.0.0-rc.21

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 (398) hide show
  1. package/CHANGELOG.md +204 -0
  2. package/dist/assets/spa/assets/index-BgoNV3TR.css +1 -0
  3. package/dist/assets/spa/assets/index-DzXygyDm.js +57 -0
  4. package/dist/assets/spa/assets/index-DzXygyDm.js.map +1 -0
  5. package/dist/assets/spa/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  6. package/dist/assets/spa/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  7. package/dist/assets/spa/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  8. package/dist/assets/spa/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  9. package/dist/assets/spa/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  10. package/dist/assets/spa/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  11. package/dist/assets/spa/assets/sora-latin-400-normal-CRt88UEn.woff2 +0 -0
  12. package/dist/assets/spa/assets/sora-latin-400-normal-OW7qkl5a.woff +0 -0
  13. package/dist/assets/spa/assets/sora-latin-500-normal-01eiPEn0.woff2 +0 -0
  14. package/dist/assets/spa/assets/sora-latin-500-normal-w58xtEt9.woff +0 -0
  15. package/dist/assets/spa/assets/sora-latin-600-normal-1_7fyUAY.woff +0 -0
  16. package/dist/assets/spa/assets/sora-latin-600-normal-Cdg4DaK0.woff2 +0 -0
  17. package/dist/assets/spa/assets/sora-latin-700-normal-9waGdLWo.woff2 +0 -0
  18. package/dist/assets/spa/assets/sora-latin-700-normal-BKPfQAnC.woff +0 -0
  19. package/dist/assets/spa/index.html +2 -8
  20. package/dist/commands/agora_doctor.d.ts +23 -0
  21. package/dist/commands/agora_doctor.d.ts.map +1 -0
  22. package/dist/commands/agora_doctor.js +110 -0
  23. package/dist/commands/agora_doctor.js.map +1 -0
  24. package/dist/commands/durable_doctor.d.ts +8 -0
  25. package/dist/commands/durable_doctor.d.ts.map +1 -0
  26. package/dist/commands/durable_doctor.js +8 -0
  27. package/dist/commands/durable_doctor.js.map +1 -0
  28. package/dist/commands/durable_worker.d.ts +3 -3
  29. package/dist/commands/durable_worker.js +7 -7
  30. package/dist/commands/durable_worker.js.map +1 -1
  31. package/dist/commands/health.d.ts +12 -0
  32. package/dist/commands/health.d.ts.map +1 -0
  33. package/dist/commands/health.js +34 -0
  34. package/dist/commands/health.js.map +1 -0
  35. package/dist/commands/lifecycle_republish.d.ts +8 -9
  36. package/dist/commands/lifecycle_republish.d.ts.map +1 -1
  37. package/dist/commands/lifecycle_republish.js +29 -16
  38. package/dist/commands/lifecycle_republish.js.map +1 -1
  39. package/dist/commands/main.d.ts +25 -5
  40. package/dist/commands/main.d.ts.map +1 -1
  41. package/dist/commands/main.js +54 -7
  42. package/dist/commands/main.js.map +1 -1
  43. package/dist/commands/runs.d.ts +9 -16
  44. package/dist/commands/runs.d.ts.map +1 -1
  45. package/dist/commands/runs.js +60 -47
  46. package/dist/commands/runs.js.map +1 -1
  47. package/dist/commands/runs_bulk.d.ts +17 -0
  48. package/dist/commands/runs_bulk.d.ts.map +1 -0
  49. package/dist/commands/runs_bulk.js +76 -0
  50. package/dist/commands/runs_bulk.js.map +1 -0
  51. package/dist/commands/runs_cancel.d.ts +15 -0
  52. package/dist/commands/runs_cancel.d.ts.map +1 -0
  53. package/dist/commands/runs_cancel.js +39 -0
  54. package/dist/commands/runs_cancel.js.map +1 -0
  55. package/dist/commands/runs_continue.d.ts +12 -0
  56. package/dist/commands/runs_continue.d.ts.map +1 -0
  57. package/dist/commands/runs_continue.js +28 -0
  58. package/dist/commands/runs_continue.js.map +1 -0
  59. package/dist/commands/runs_event.d.ts +13 -0
  60. package/dist/commands/runs_event.d.ts.map +1 -0
  61. package/dist/commands/runs_event.js +28 -0
  62. package/dist/commands/runs_event.js.map +1 -0
  63. package/dist/commands/runs_fan.d.ts +16 -0
  64. package/dist/commands/runs_fan.d.ts.map +1 -0
  65. package/dist/commands/runs_fan.js +58 -0
  66. package/dist/commands/runs_fan.js.map +1 -0
  67. package/dist/commands/runs_redispatch.d.ts +14 -0
  68. package/dist/commands/runs_redispatch.d.ts.map +1 -0
  69. package/dist/commands/runs_redispatch.js +35 -0
  70. package/dist/commands/runs_redispatch.js.map +1 -0
  71. package/dist/commands/runs_replay.d.ts +14 -0
  72. package/dist/commands/runs_replay.d.ts.map +1 -0
  73. package/dist/commands/runs_replay.js +38 -0
  74. package/dist/commands/runs_replay.js.map +1 -0
  75. package/dist/commands/runs_retry.d.ts +13 -0
  76. package/dist/commands/runs_retry.d.ts.map +1 -0
  77. package/dist/commands/runs_retry.js +29 -0
  78. package/dist/commands/runs_retry.js.map +1 -0
  79. package/dist/commands/runs_show.d.ts +14 -0
  80. package/dist/commands/runs_show.d.ts.map +1 -0
  81. package/dist/commands/runs_show.js +96 -0
  82. package/dist/commands/runs_show.js.map +1 -0
  83. package/dist/commands/runs_signal.d.ts +17 -0
  84. package/dist/commands/runs_signal.d.ts.map +1 -0
  85. package/dist/commands/runs_signal.js +49 -0
  86. package/dist/commands/runs_signal.js.map +1 -0
  87. package/dist/commands/runs_update.d.ts +15 -0
  88. package/dist/commands/runs_update.d.ts.map +1 -0
  89. package/dist/commands/runs_update.js +39 -0
  90. package/dist/commands/runs_update.js.map +1 -0
  91. package/dist/commands/runs_values.d.ts +15 -0
  92. package/dist/commands/runs_values.d.ts.map +1 -0
  93. package/dist/commands/runs_values.js +45 -0
  94. package/dist/commands/runs_values.js.map +1 -0
  95. package/dist/commands/schedules.d.ts +18 -0
  96. package/dist/commands/schedules.d.ts.map +1 -0
  97. package/dist/commands/schedules.js +70 -0
  98. package/dist/commands/schedules.js.map +1 -0
  99. package/dist/commands/schedules_pause.d.ts +16 -0
  100. package/dist/commands/schedules_pause.d.ts.map +1 -0
  101. package/dist/commands/schedules_pause.js +40 -0
  102. package/dist/commands/schedules_pause.js.map +1 -0
  103. package/dist/commands/schedules_resume.d.ts +8 -0
  104. package/dist/commands/schedules_resume.d.ts.map +1 -0
  105. package/dist/commands/schedules_resume.js +8 -0
  106. package/dist/commands/schedules_resume.js.map +1 -0
  107. package/dist/commands/schedules_trigger.d.ts +13 -0
  108. package/dist/commands/schedules_trigger.d.ts.map +1 -0
  109. package/dist/commands/schedules_trigger.js +32 -0
  110. package/dist/commands/schedules_trigger.js.map +1 -0
  111. package/dist/commands/tasks_complete.d.ts +15 -0
  112. package/dist/commands/tasks_complete.d.ts.map +1 -0
  113. package/dist/commands/tasks_complete.js +41 -0
  114. package/dist/commands/tasks_complete.js.map +1 -0
  115. package/dist/commands/tasks_fail.d.ts +16 -0
  116. package/dist/commands/tasks_fail.d.ts.map +1 -0
  117. package/dist/commands/tasks_fail.js +47 -0
  118. package/dist/commands/tasks_fail.js.map +1 -0
  119. package/dist/commands/workers.d.ts +11 -0
  120. package/dist/commands/workers.d.ts.map +1 -0
  121. package/dist/commands/workers.js +35 -0
  122. package/dist/commands/workers.js.map +1 -0
  123. package/dist/commands/workflows_lifecycle.d.ts +12 -0
  124. package/dist/commands/workflows_lifecycle.d.ts.map +1 -0
  125. package/dist/commands/workflows_lifecycle.js +37 -0
  126. package/dist/commands/workflows_lifecycle.js.map +1 -0
  127. package/dist/configure.d.ts +2 -2
  128. package/dist/configure.js +2 -2
  129. package/dist/providers/dashboard_provider.d.ts +57 -54
  130. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  131. package/dist/providers/dashboard_provider.js +399 -288
  132. package/dist/providers/dashboard_provider.js.map +1 -1
  133. package/dist/providers/durable_provider.d.ts.map +1 -1
  134. package/dist/providers/durable_provider.js +67 -37
  135. package/dist/providers/durable_provider.js.map +1 -1
  136. package/dist/src/commands/console_command.d.ts +82 -0
  137. package/dist/src/commands/console_command.d.ts.map +1 -0
  138. package/dist/src/commands/console_command.js +238 -0
  139. package/dist/src/commands/console_command.js.map +1 -0
  140. package/dist/src/commands/runs.js +2 -2
  141. package/dist/src/commands/runs.js.map +1 -1
  142. package/dist/src/concurrency.d.ts.map +1 -1
  143. package/dist/src/concurrency.js +1 -0
  144. package/dist/src/concurrency.js.map +1 -1
  145. package/dist/src/config_types.d.ts +79 -26
  146. package/dist/src/config_types.d.ts.map +1 -1
  147. package/dist/src/dashboard/access_denied_page.d.ts +5 -7
  148. package/dist/src/dashboard/access_denied_page.d.ts.map +1 -1
  149. package/dist/src/dashboard/access_denied_page.js +2 -6
  150. package/dist/src/dashboard/access_denied_page.js.map +1 -1
  151. package/dist/src/dashboard/audit.d.ts +14 -0
  152. package/dist/src/dashboard/audit.d.ts.map +1 -0
  153. package/dist/src/dashboard/audit.js +36 -0
  154. package/dist/src/dashboard/audit.js.map +1 -0
  155. package/dist/src/dashboard/console_authorize.d.ts +98 -0
  156. package/dist/src/dashboard/console_authorize.d.ts.map +1 -0
  157. package/dist/src/dashboard/console_authorize.js +163 -0
  158. package/dist/src/dashboard/console_authorize.js.map +1 -0
  159. package/dist/src/dashboard/console_shell.d.ts +45 -0
  160. package/dist/src/dashboard/console_shell.d.ts.map +1 -0
  161. package/dist/src/dashboard/console_shell.js +125 -0
  162. package/dist/src/dashboard/console_shell.js.map +1 -0
  163. package/dist/src/dashboard/console_switcher.d.ts +132 -0
  164. package/dist/src/dashboard/console_switcher.d.ts.map +1 -0
  165. package/dist/src/dashboard/console_switcher.js +201 -0
  166. package/dist/src/dashboard/console_switcher.js.map +1 -0
  167. package/dist/src/dashboard/define_config.d.ts +68 -81
  168. package/dist/src/dashboard/define_config.d.ts.map +1 -1
  169. package/dist/src/dashboard/define_config.js +18 -112
  170. package/dist/src/dashboard/define_config.js.map +1 -1
  171. package/dist/src/dashboard/dev_tools.d.ts +102 -0
  172. package/dist/src/dashboard/dev_tools.d.ts.map +1 -0
  173. package/dist/src/dashboard/dev_tools.js +502 -0
  174. package/dist/src/dashboard/dev_tools.js.map +1 -0
  175. package/dist/src/dashboard/diagnostics-recorder.d.ts +1 -1
  176. package/dist/src/dashboard/diagnostics-recorder.js +1 -1
  177. package/dist/src/dashboard/gateway-adapter.d.ts +6 -6
  178. package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -1
  179. package/dist/src/dashboard/gateway-adapter.js +5 -5
  180. package/dist/src/dashboard/gateway-adapter.js.map +1 -1
  181. package/dist/src/dashboard/handlers.d.ts +48 -39
  182. package/dist/src/dashboard/handlers.d.ts.map +1 -1
  183. package/dist/src/dashboard/handlers.js +181 -83
  184. package/dist/src/dashboard/handlers.js.map +1 -1
  185. package/dist/src/dashboard/index.d.ts +12 -9
  186. package/dist/src/dashboard/index.d.ts.map +1 -1
  187. package/dist/src/dashboard/index.js +10 -7
  188. package/dist/src/dashboard/index.js.map +1 -1
  189. package/dist/src/dashboard/list-filters.d.ts +27 -0
  190. package/dist/src/dashboard/list-filters.d.ts.map +1 -0
  191. package/dist/src/dashboard/list-filters.js +69 -0
  192. package/dist/src/dashboard/list-filters.js.map +1 -0
  193. package/dist/src/dashboard/openapi.d.ts.map +1 -1
  194. package/dist/src/dashboard/openapi.js +127 -52
  195. package/dist/src/dashboard/openapi.js.map +1 -1
  196. package/dist/src/dashboard/run-filter.d.ts +18 -15
  197. package/dist/src/dashboard/run-filter.d.ts.map +1 -1
  198. package/dist/src/dashboard/run-filter.js +30 -22
  199. package/dist/src/dashboard/run-filter.js.map +1 -1
  200. package/dist/src/dashboard/spa.d.ts +26 -1
  201. package/dist/src/dashboard/spa.d.ts.map +1 -1
  202. package/dist/src/dashboard/spa.js +9 -2
  203. package/dist/src/dashboard/spa.js.map +1 -1
  204. package/dist/src/dashboard/tenant_scope.d.ts +38 -0
  205. package/dist/src/dashboard/tenant_scope.d.ts.map +1 -0
  206. package/dist/src/dashboard/tenant_scope.js +166 -0
  207. package/dist/src/dashboard/tenant_scope.js.map +1 -0
  208. package/dist/src/define_config.d.ts +27 -12
  209. package/dist/src/define_config.d.ts.map +1 -1
  210. package/dist/src/define_config.js +9 -4
  211. package/dist/src/define_config.js.map +1 -1
  212. package/dist/src/diagnostics/index.d.ts +11 -0
  213. package/dist/src/diagnostics/index.d.ts.map +1 -0
  214. package/dist/src/diagnostics/index.js +6 -0
  215. package/dist/src/diagnostics/index.js.map +1 -0
  216. package/dist/src/diagnostics/transport.d.ts +71 -0
  217. package/dist/src/diagnostics/transport.d.ts.map +1 -0
  218. package/dist/src/diagnostics/transport.js +188 -0
  219. package/dist/src/diagnostics/transport.js.map +1 -0
  220. package/dist/src/diagnostics-bridge.d.ts +2 -1
  221. package/dist/src/diagnostics-bridge.d.ts.map +1 -1
  222. package/dist/src/diagnostics-bridge.js +7 -1
  223. package/dist/src/diagnostics-bridge.js.map +1 -1
  224. package/dist/src/doctor/agora_doctor.d.ts +116 -0
  225. package/dist/src/doctor/agora_doctor.d.ts.map +1 -0
  226. package/dist/src/doctor/agora_doctor.js +685 -0
  227. package/dist/src/doctor/agora_doctor.js.map +1 -0
  228. package/dist/src/doctor/checks.d.ts +28 -0
  229. package/dist/src/doctor/checks.d.ts.map +1 -0
  230. package/dist/src/doctor/checks.js +288 -0
  231. package/dist/src/doctor/checks.js.map +1 -0
  232. package/dist/src/doctor/index.d.ts +9 -0
  233. package/dist/src/doctor/index.d.ts.map +1 -0
  234. package/dist/src/doctor/index.js +16 -0
  235. package/dist/src/doctor/index.js.map +1 -0
  236. package/dist/src/engine.d.ts +55 -9
  237. package/dist/src/engine.d.ts.map +1 -1
  238. package/dist/src/engine.js +333 -84
  239. package/dist/src/engine.js.map +1 -1
  240. package/dist/src/errors.d.ts +9 -0
  241. package/dist/src/errors.d.ts.map +1 -1
  242. package/dist/src/errors.js +13 -0
  243. package/dist/src/errors.js.map +1 -1
  244. package/dist/src/event-trigger-bridge.d.ts +5 -14
  245. package/dist/src/event-trigger-bridge.d.ts.map +1 -1
  246. package/dist/src/event-trigger-bridge.js +11 -99
  247. package/dist/src/event-trigger-bridge.js.map +1 -1
  248. package/dist/src/index.d.ts +8 -5
  249. package/dist/src/index.d.ts.map +1 -1
  250. package/dist/src/index.js +6 -4
  251. package/dist/src/index.js.map +1 -1
  252. package/dist/src/interfaces.d.ts +53 -9
  253. package/dist/src/interfaces.d.ts.map +1 -1
  254. package/dist/src/interfaces.js +8 -0
  255. package/dist/src/interfaces.js.map +1 -1
  256. package/dist/src/origin.js +3 -3
  257. package/dist/src/role_bindings.d.ts +8 -8
  258. package/dist/src/role_bindings.js +8 -8
  259. package/dist/src/run-gateway/interface.d.ts +4 -3
  260. package/dist/src/run-gateway/interface.d.ts.map +1 -1
  261. package/dist/src/run-gateway/proxy-run-gateway.d.ts +4 -2
  262. package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -1
  263. package/dist/src/run-gateway/proxy-run-gateway.js +21 -6
  264. package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -1
  265. package/dist/src/run-gateway/run-request-responder.d.ts +6 -1
  266. package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -1
  267. package/dist/src/run-gateway/run-request-responder.js +30 -3
  268. package/dist/src/run-gateway/run-request-responder.js.map +1 -1
  269. package/dist/src/run-gateway/store-run-gateway.d.ts +4 -4
  270. package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -1
  271. package/dist/src/run-gateway/store-run-gateway.js +4 -4
  272. package/dist/src/run-gateway/store-run-gateway.js.map +1 -1
  273. package/dist/src/run-gateway/tenant-auth.d.ts +19 -1
  274. package/dist/src/run-gateway/tenant-auth.d.ts.map +1 -1
  275. package/dist/src/run-gateway/tenant-auth.js +133 -0
  276. package/dist/src/run-gateway/tenant-auth.js.map +1 -1
  277. package/dist/src/run-gateway/tenant-scoped-gateway.d.ts +10 -0
  278. package/dist/src/run-gateway/tenant-scoped-gateway.d.ts.map +1 -0
  279. package/dist/src/run-gateway/tenant-scoped-gateway.js +107 -0
  280. package/dist/src/run-gateway/tenant-scoped-gateway.js.map +1 -0
  281. package/dist/src/run-value-facets.d.ts +1 -1
  282. package/dist/src/run-value-facets.d.ts.map +1 -1
  283. package/dist/src/run-value-facets.js +5 -1
  284. package/dist/src/run-value-facets.js.map +1 -1
  285. package/dist/src/scheduler.d.ts.map +1 -1
  286. package/dist/src/scheduler.js +2 -1
  287. package/dist/src/scheduler.js.map +1 -1
  288. package/dist/src/schedules.d.ts +16 -1
  289. package/dist/src/schedules.d.ts.map +1 -1
  290. package/dist/src/schedules.js +29 -4
  291. package/dist/src/schedules.js.map +1 -1
  292. package/dist/src/services/main.d.ts +3 -3
  293. package/dist/src/services/main.js +6 -6
  294. package/dist/src/services/main.js.map +1 -1
  295. package/dist/src/singleton-admission.d.ts.map +1 -1
  296. package/dist/src/singleton-admission.js +2 -0
  297. package/dist/src/singleton-admission.js.map +1 -1
  298. package/dist/src/singleton-gate.d.ts +1 -1
  299. package/dist/src/singleton-gate.d.ts.map +1 -1
  300. package/dist/src/singleton-gate.js +4 -3
  301. package/dist/src/singleton-gate.js.map +1 -1
  302. package/dist/src/sql-schema.d.ts +54 -0
  303. package/dist/src/sql-schema.d.ts.map +1 -0
  304. package/dist/src/sql-schema.js +145 -0
  305. package/dist/src/sql-schema.js.map +1 -0
  306. package/dist/src/stores/factory.d.ts +6 -0
  307. package/dist/src/stores/factory.d.ts.map +1 -1
  308. package/dist/src/stores/factory.js +3 -0
  309. package/dist/src/stores/factory.js.map +1 -1
  310. package/dist/src/stores/lucid-mappers.d.ts +3 -0
  311. package/dist/src/stores/lucid-mappers.d.ts.map +1 -1
  312. package/dist/src/stores/lucid-mappers.js +12 -0
  313. package/dist/src/stores/lucid-mappers.js.map +1 -1
  314. package/dist/src/stores/lucid-pg-hot-path.d.ts +6 -2
  315. package/dist/src/stores/lucid-pg-hot-path.d.ts.map +1 -1
  316. package/dist/src/stores/lucid-pg-hot-path.js +7 -3
  317. package/dist/src/stores/lucid-pg-hot-path.js.map +1 -1
  318. package/dist/src/stores/lucid-schema.d.ts +18 -4
  319. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  320. package/dist/src/stores/lucid-schema.js +106 -61
  321. package/dist/src/stores/lucid-schema.js.map +1 -1
  322. package/dist/src/stores/lucid.d.ts +42 -2
  323. package/dist/src/stores/lucid.d.ts.map +1 -1
  324. package/dist/src/stores/lucid.js +450 -231
  325. package/dist/src/stores/lucid.js.map +1 -1
  326. package/dist/src/tenancy.d.ts +104 -0
  327. package/dist/src/tenancy.d.ts.map +1 -0
  328. package/dist/src/tenancy.js +91 -0
  329. package/dist/src/tenancy.js.map +1 -0
  330. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  331. package/dist/src/testing/in-memory-state-store.js +9 -3
  332. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  333. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  334. package/dist/src/testing-kit/state-store-conformance.js +59 -0
  335. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  336. package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -1
  337. package/dist/src/testing-kit/transport-conformance.js +5 -1
  338. package/dist/src/testing-kit/transport-conformance.js.map +1 -1
  339. package/dist/src/transports/bullmq/bullmq-transport.d.ts +1 -1
  340. package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -1
  341. package/dist/src/transports/bullmq/bullmq-transport.js +14 -3
  342. package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -1
  343. package/dist/src/transports/db-schema.d.ts +13 -5
  344. package/dist/src/transports/db-schema.d.ts.map +1 -1
  345. package/dist/src/transports/db-schema.js +52 -27
  346. package/dist/src/transports/db-schema.js.map +1 -1
  347. package/dist/src/transports/db.d.ts +6 -0
  348. package/dist/src/transports/db.d.ts.map +1 -1
  349. package/dist/src/transports/db.js +91 -42
  350. package/dist/src/transports/db.js.map +1 -1
  351. package/dist/src/transports/factory.d.ts +16 -0
  352. package/dist/src/transports/factory.d.ts.map +1 -1
  353. package/dist/src/transports/factory.js +6 -1
  354. package/dist/src/transports/factory.js.map +1 -1
  355. package/dist/src/transports/queue.d.ts +2 -2
  356. package/dist/src/transports/queue.d.ts.map +1 -1
  357. package/dist/src/transports/queue.js +49 -8
  358. package/dist/src/transports/queue.js.map +1 -1
  359. package/dist/src/transports/worker-keys-db.d.ts +5 -0
  360. package/dist/src/transports/worker-keys-db.d.ts.map +1 -1
  361. package/dist/src/transports/worker-keys-db.js +10 -8
  362. package/dist/src/transports/worker-keys-db.js.map +1 -1
  363. package/dist/src/workflow-discovery.d.ts +1 -1
  364. package/dist/src/workflow-discovery.d.ts.map +1 -1
  365. package/dist/src/workflow-discovery.js +4 -7
  366. package/dist/src/workflow-discovery.js.map +1 -1
  367. package/dist/src/workflow-events.d.ts +20 -51
  368. package/dist/src/workflow-events.d.ts.map +1 -1
  369. package/dist/src/workflow-events.js +12 -30
  370. package/dist/src/workflow-events.js.map +1 -1
  371. package/dist/src/workflow-ref.d.ts +1 -1
  372. package/dist/stubs/config/durable_dashboard.stub +9 -29
  373. package/dist/stubs/database/migrations/create_durable_tables.stub +12 -2
  374. package/dist/stubs/database/migrations/create_durable_transport_messages.stub +34 -5
  375. package/dist/stubs/database/migrations/create_durable_transport_tables.stub +30 -8
  376. package/dist/stubs/database/migrations/create_durable_transport_worker_keys.stub +24 -2
  377. package/package.json +25 -7
  378. package/skills/durable-cluster/SKILL.md +43 -23
  379. package/skills/durable-reliability/SKILL.md +13 -7
  380. package/dist/assets/spa/assets/index-70hGzOnT.css +0 -1
  381. package/dist/assets/spa/assets/index-CJjMAUau.js +0 -16
  382. package/dist/assets/spa/assets/index-CJjMAUau.js.map +0 -1
  383. package/dist/commands/retry.d.ts +0 -16
  384. package/dist/commands/retry.d.ts.map +0 -1
  385. package/dist/commands/retry.js +0 -34
  386. package/dist/commands/retry.js.map +0 -1
  387. package/dist/src/dashboard/auth.d.ts +0 -134
  388. package/dist/src/dashboard/auth.d.ts.map +0 -1
  389. package/dist/src/dashboard/auth.js +0 -156
  390. package/dist/src/dashboard/auth.js.map +0 -1
  391. package/dist/src/dashboard/login_page.d.ts +0 -36
  392. package/dist/src/dashboard/login_page.d.ts.map +0 -1
  393. package/dist/src/dashboard/login_page.js +0 -158
  394. package/dist/src/dashboard/login_page.js.map +0 -1
  395. package/dist/src/dashboard/session_cookie.d.ts +0 -45
  396. package/dist/src/dashboard/session_cookie.d.ts.map +0 -1
  397. package/dist/src/dashboard/session_cookie.js +0 -97
  398. package/dist/src/dashboard/session_cookie.js.map +0 -1
@@ -3,18 +3,24 @@ import { basename, resolve as resolvePath } from 'node:path';
3
3
  import { PassThrough } from 'node:stream';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { resolveAccessDeniedPage, } from '../src/dashboard/access_denied_page.js';
6
- import { performLogin, performSession, readSession, SESSION_COOKIE_NAME, sanitizeReturnTo, } from '../src/dashboard/auth.js';
6
+ import { recordConsoleAudit } from '../src/dashboard/audit.js';
7
7
  import { compat, enumerateLiveFleet, mergeFleets, } from '../src/dashboard/compat.js';
8
- import { isProduction, normalizeAuthorizeResult, resolveConfig, } from '../src/dashboard/define_config.js';
8
+ import { resolveSignOut, resolveViewer } from '../src/dashboard/console_shell.js';
9
+ import { durableConsoleSearch, listVisibleConsoles, otherConsoles, readAgoraConsoles, registerAgoraConsole, searchConsoles, } from '../src/dashboard/console_switcher.js';
10
+ import { NO_AUTHORIZE_WARNING, resolveConfig, } from '../src/dashboard/define_config.js';
11
+ import { registerDurableDevTools } from '../src/dashboard/dev_tools.js';
9
12
  import { BlockedDiagnosticsRecorder } from '../src/dashboard/diagnostics-recorder.js';
10
- import { dashboardEngineForRole } from '../src/dashboard/gateway-adapter.js';
13
+ import { dashboardEngineForTopology } from '../src/dashboard/gateway-adapter.js';
11
14
  import { bulkAction, cancelRun, completeTaskRun, continueRun, deliverWebhook, failTaskRun, getFanMembers, getRun, getRunEvent, health, listRuns, listSchedules, metricsText, redispatchPendingRun, republishLifecycleRun, retryRun, retryWithInputRun, runValues, scheduleAction, signalRun, topology, updateRun, workers, workflowLifecycle, } from '../src/dashboard/handlers.js';
12
- import { renderLoginPage } from '../src/dashboard/login_page.js';
13
15
  import { openApiDocument } from '../src/dashboard/openapi.js';
14
16
  import { contentTypeFor, renderIndexHtml } from '../src/dashboard/spa.js';
17
+ import { resolveConsoleTenantScope, scopedDashboardEngine, } from '../src/dashboard/tenant_scope.js';
18
+ import { registerDoctorChecks } from '../src/doctor/agora_doctor.js';
19
+ import { durableConsoleDoctorChecks } from '../src/doctor/checks.js';
15
20
  import { WorkflowEngine } from '../src/index.js';
16
21
  import { collectMetrics } from '../src/metrics.js';
17
22
  import { DURABLE_TRANSPORT } from '../src/role_bindings.js';
23
+ import { tenancyEnabled, tenancySlot } from '../src/tenancy.js';
18
24
  /**
19
25
  * Directory of the built SPA (`dist/spa`, copied in by `copy:stubs` — see `package.json`), relative to
20
26
  * this compiled provider (`dist/providers`). Resolved lazily (not at module scope) for the same reason
@@ -22,6 +28,8 @@ import { DURABLE_TRANSPORT } from '../src/role_bindings.js';
22
28
  * on any non-`file:` `import.meta.url`, which would make this provider unimportable under a test
23
29
  * runner that doesn't use real file URLs.
24
30
  */
31
+ /** This console's id in the app's `'agora.consoles'` registry. */
32
+ const CONSOLE_ID = 'durable';
25
33
  let spaDir;
26
34
  function spaDirectory() {
27
35
  spaDir ??= fileURLToPath(new URL('../assets/spa/', import.meta.url));
@@ -57,8 +65,10 @@ function spaDirectory() {
57
65
  * - `POST /api/bulk/:action` -> bulk retry/cancel every run matching a filter
58
66
  * - `GET /api/health` -> worker-group health (compact shape)
59
67
  * - `GET /api/workers` -> worker-group health (full heartbeats; SPA)
60
- * - `GET /api/topology` -> this deployment's durable role
68
+ * - `GET /api/topology` -> this deployment's durable topology
61
69
  * - `GET /api/compat` -> fleet health / protocol-compatibility panel
70
+ * - `GET /api/consoles` -> the other Agora consoles this viewer may open (switcher)
71
+ * - `GET /api/consoles/search?q=` -> search across those consoles (and this one)
62
72
  * - `GET /api/openapi.json` -> the machine-readable API contract (OpenAPI 3.1)
63
73
  */
64
74
  export default class DashboardProvider {
@@ -66,65 +76,229 @@ export default class DashboardProvider {
66
76
  constructor(app) {
67
77
  this.app = app;
68
78
  }
69
- /** Warn once so a throwing `login`/`session` hook doesn't spam the logs on every failed attempt. */
70
- warnedOnHookThrow = false;
71
79
  warnedOnAuthorizeThrow = false;
72
- /** Login-attempt timestamps per client address — a simple sliding window (10/min) so a brute
73
- * force can't hammer the host's `login` hook at line rate. In-memory per instance: a cluster
74
- * multiplies the budget by pod count, which still reduces the attack by orders of magnitude. */
75
- loginAttempts = new Map();
76
- /** True (and reply 429) when this address exhausted its login window. */
77
- loginRateLimited(ctx) {
78
- const WINDOW_MS = 60_000;
79
- const MAX_ATTEMPTS = 10;
80
- const key = ctx.request.ip() || 'unknown';
81
- const now = Date.now();
82
- const recent = (this.loginAttempts.get(key) ?? []).filter((t) => now - t < WINDOW_MS);
83
- if (recent.length >= MAX_ATTEMPTS) {
84
- this.loginAttempts.set(key, recent);
85
- ctx.response.status(429).json({ error: 'too many login attempts — retry in a minute' });
86
- return true;
87
- }
88
- recent.push(now);
89
- this.loginAttempts.set(key, recent);
90
- // Bounded: prune other addresses opportunistically so the map can't grow without limit.
91
- if (this.loginAttempts.size > 10_000)
92
- this.loginAttempts.clear();
93
- return false;
94
- }
80
+ /** Each request's `authorize` verdict, read again for the console's tenant scope. */
81
+ decisions = new WeakMap();
82
+ /** Each request's resolved tenant scope. */
83
+ scopes = new WeakMap();
95
84
  /** SSE connections currently open, per client address (and in total) — see streamRun. */
96
85
  sseByAddress = new Map();
97
86
  sseTotal = 0;
98
87
  /**
99
88
  * Captures the engine's `capability.unavailable` / `protocol.incompatible` diagnostics events so the
100
89
  * `/compat` health panel can render each blocked run's structured delta + the live-fleet compat view
101
- * (design §7.6, §10). Store-role only — attached in {@link boot}; a `tenant` pod owns no engine.
90
+ * (design §7.6, §10). Store-topology only — attached in {@link boot}; a `tenant` pod owns no engine.
102
91
  */
103
92
  diagnostics = new BlockedDiagnosticsRecorder();
104
93
  /** Detach handle for the diagnostics subscription, released on {@link shutdown}. */
105
94
  detachDiagnostics = null;
106
- /** This process's event counters for `GET /metrics` (store roles; a tenant pod has no engine). */
95
+ /** This process's event counters for `GET /metrics` (store topologies; a tenant pod has no engine). */
107
96
  metricsCollector = null;
108
- /** The active durable role (`standalone` when no `durable` config is present — today's default). */
109
- durableRole() {
110
- return this.app.config.get('durable', {}).role ?? 'standalone';
97
+ /** The active durable topology (`standalone` when no `durable` config is present — today's default). */
98
+ durableTopology() {
99
+ return this.app.config.get('durable', {}).topology ?? 'standalone';
111
100
  }
112
101
  async boot() {
113
- const config = resolveConfig(this.app.config.get('durable_dashboard', {}));
102
+ await registerDoctorChecks(this.app.container, durableConsoleDoctorChecks).catch(() => undefined);
103
+ const config = resolveConfig(this.app.config.get('durable_dashboard', {}), { inProduction: this.app.inProduction });
114
104
  if (!config.enabled)
115
105
  return;
116
- const role = this.durableRole();
106
+ if (!config.authorizeConfigured)
107
+ await this.warnNoAuthorize();
108
+ const durableTopology = this.durableTopology();
109
+ // Developer tools on the app's dev MCP endpoint, when `@adonis-agora/agent/mcp_provider` bound
110
+ // its hub (its `register()` ran before any `boot()`). Gated by this console's own `authorize`.
111
+ await this.registerDevTools(config, durableTopology);
112
+ // The console switcher: this console joins the app's `'agora.consoles'` registry so the other
113
+ // Agora consoles can link to it (and search it), and it can link to them.
114
+ await this.registerConsole(config, durableTopology);
117
115
  // Route registration can't happen synchronously in `boot()` — see the long-standing note this
118
116
  // provider always carried: the router singleton isn't committed until the app's "booted" hooks
119
117
  // run, which fire strictly AFTER every provider's own `boot()`.
120
118
  await this.app.booted(async () => {
121
119
  const router = await this.app.container.make('router');
122
- this.registerRoutes(router, config, role);
123
- await this.attachDiagnostics(role);
120
+ this.registerRoutes(router, config, durableTopology);
121
+ await this.attachDiagnostics(durableTopology);
122
+ });
123
+ }
124
+ /**
125
+ * The console's read/control port, resolved per request (see {@link registerRoutes}). With tenancy
126
+ * on and a request at hand, it is confined to the tenants this viewer may see (see
127
+ * {@link consoleTenantScope}); the process counters, which span every tenant, are then left out
128
+ * unless the viewer sees every tenant.
129
+ */
130
+ consoleDeps(config, durableTopology) {
131
+ return async (ctx) => {
132
+ const base = await dashboardEngineForTopology(durableTopology, this.app.container, WorkflowEngine);
133
+ const scope = ctx ? await this.consoleTenantScope(config, ctx) : undefined;
134
+ const slot = scope ? tenancySlot() : undefined;
135
+ const engine = scope
136
+ ? scopedDashboardEngine(base, scope.ids, slot ? (fn) => slot.allTenants(fn) : undefined)
137
+ : base;
138
+ const metrics = scope && scope.ids !== undefined ? undefined : this.metricsCollector;
139
+ return {
140
+ engine,
141
+ ...(config.redact ? { redact: config.redact } : {}),
142
+ ...(metrics ? { metrics } : {}),
143
+ };
144
+ };
145
+ }
146
+ /** Whether tenancy is on for durable (config/durable.ts `tenancy`, else config/context.ts). */
147
+ tenancyOn() {
148
+ return tenancyEnabled(this.app.config.get('durable', {}).tenancy);
149
+ }
150
+ /**
151
+ * The tenants this viewer may see in the console, once per request, or `undefined` with tenancy
152
+ * off. `authorize` may grant `tenants` (`'*'` or a list); otherwise it is the viewer's own active
153
+ * tenant. A `?tenant=` pick outside the grant is answered with nothing and audited.
154
+ */
155
+ async consoleTenantScope(config, ctx) {
156
+ if (!this.tenancyOn())
157
+ return undefined;
158
+ const known = this.scopes.get(ctx);
159
+ if (known)
160
+ return known;
161
+ let decision = this.decisions.get(ctx);
162
+ if (decision === undefined) {
163
+ decision = await config.authorize(ctx);
164
+ this.decisions.set(ctx, decision);
165
+ }
166
+ const granted = decision.allowed ? decision.tenants : undefined;
167
+ const slot = tenancySlot();
168
+ const option = this.app.config.get('durable', {}).tenancy;
169
+ const kinds = typeof option === 'object' ? option.kinds : undefined;
170
+ // `tenancy.required` makes a missing tenant throw: in the console that is just "no organization".
171
+ let filter;
172
+ try {
173
+ filter = slot?.tenantFilter(kinds ? { kinds } : undefined);
174
+ }
175
+ catch {
176
+ filter = undefined;
177
+ }
178
+ const own = filter?.mode === 'tenant'
179
+ ? filter.tenant
180
+ : undefined;
181
+ const picked = ctx.request.qs().tenant;
182
+ const scope = resolveConsoleTenantScope({
183
+ granted,
184
+ own: own ? { id: own.id, kind: own.kind, slug: own.slug } : undefined,
185
+ selected: typeof picked === 'string' ? picked : undefined,
186
+ });
187
+ if (scope.forged) {
188
+ this.recordAudit(config, {
189
+ at: new Date(),
190
+ actor: auditActor(ctx),
191
+ method: 'TENANT',
192
+ path: `${ctx.request.url(false)} outside scope: tenant=${scope.selected ?? ''}`,
193
+ });
194
+ }
195
+ this.scopes.set(ctx, scope);
196
+ return scope;
197
+ }
198
+ async registerDevTools(config, durableTopology) {
199
+ const deps = this.consoleDeps(config, durableTopology);
200
+ // An auxiliary feature: a hub that throws must not take the app's boot down with it.
201
+ try {
202
+ await this.registerDurableDevToolsInto(config, deps);
203
+ }
204
+ catch (error) {
205
+ try {
206
+ const logger = await this.app.container.make('logger');
207
+ logger.warn(`[durable_dashboard] could not register the developer tools: ${error instanceof Error ? error.message : String(error)}`);
208
+ }
209
+ catch {
210
+ /* logging must never break boot */
211
+ }
212
+ }
213
+ }
214
+ async registerDurableDevToolsInto(config, deps) {
215
+ await registerDurableDevTools(this.app.container, {
216
+ authorize: config.authorize,
217
+ deps,
218
+ audit: (entry) => this.recordAudit(config, {
219
+ at: new Date(),
220
+ actor: entry.actor,
221
+ method: 'MCP',
222
+ path: `${entry.tool} ${entry.target}`,
223
+ }),
224
+ compat: async () => {
225
+ const result = await compat(await this.compatSource((await deps()).engine));
226
+ return result.body;
227
+ },
124
228
  });
125
229
  }
126
- async attachDiagnostics(role) {
127
- if (role === 'tenant')
230
+ async registerConsole(config, durableTopology) {
231
+ const deps = this.consoleDeps(config, durableTopology);
232
+ let logger;
233
+ try {
234
+ logger = await this.app.container.make('logger');
235
+ }
236
+ catch {
237
+ logger = undefined;
238
+ }
239
+ await registerAgoraConsole(this.app.container, {
240
+ id: CONSOLE_ID,
241
+ title: 'Workflows',
242
+ package: '@adonis-agora/durable',
243
+ icon: 'workflow',
244
+ path: config.path === '' ? '/' : config.path,
245
+ order: 10,
246
+ authorize: config.authorize,
247
+ search: durableConsoleSearch(async (ctx) => {
248
+ const d = await deps(ctx);
249
+ return {
250
+ getRun: (id) => d.engine.getRun(id),
251
+ workflows: async (q, limit) => {
252
+ const res = await runValues(d, {
253
+ params: {},
254
+ query: { field: 'workflow', search: q, limit: String(limit) },
255
+ });
256
+ return res.status === 200
257
+ ? res.body
258
+ : [];
259
+ },
260
+ };
261
+ }, config.path),
262
+ }, logger);
263
+ }
264
+ /**
265
+ * What the console's "Organization" control shows: off (tenancy off), `none` (a viewer with no
266
+ * tenant), `fixed` (one tenant), or `pick` (several, or every tenant with `all`).
267
+ */
268
+ async tenantPicker(config, ctx) {
269
+ const scope = await this.consoleTenantScope(config, ctx);
270
+ if (!scope)
271
+ return { mode: 'off', all: false, selected: null, tenants: [] };
272
+ if (scope.authorized === 'none')
273
+ return { mode: 'none', all: false, selected: null, tenants: [] };
274
+ let ids;
275
+ if (scope.authorized === '*') {
276
+ // Every tenant that has runs, read across tenants.
277
+ const base = await dashboardEngineForTopology(this.durableTopology(), this.app.container, WorkflowEngine);
278
+ const slot = tenancySlot();
279
+ // Paged: every organization that has runs, not only the first page of them.
280
+ ids = [];
281
+ const facets = base.runValueFacets?.bind(base);
282
+ for (let offset = 0; facets && offset < 10_000; offset += 200) {
283
+ const page = () => facets({ field: 'tenant' }, {}, { limit: 200, offset });
284
+ const rows = (await (slot ? slot.allTenants(page) : page())) ?? [];
285
+ for (const row of rows)
286
+ if (typeof row.value === 'string')
287
+ ids.push(row.value);
288
+ if (rows.length < 200)
289
+ break;
290
+ }
291
+ }
292
+ else {
293
+ ids = scope.authorized;
294
+ }
295
+ const labels = await tenantLabelsFor(ids, scope.own);
296
+ const tenants = ids.map((id) => ({ id, label: labels[id] ?? id }));
297
+ const mode = scope.authorized === '*' || ids.length > 1 ? 'pick' : 'fixed';
298
+ return { mode, all: scope.authorized === '*', selected: scope.selected ?? null, tenants };
299
+ }
300
+ async attachDiagnostics(durableTopology) {
301
+ if (durableTopology === 'tenant')
128
302
  return;
129
303
  // A warmed-up app (`node ace codegen`) never starts nor shuts down: resolving the engine would open
130
304
  // the transport/store connections nothing closes, and the command would never exit. The routes are
@@ -142,23 +316,12 @@ export default class DashboardProvider {
142
316
  // No store-backed engine available — the compat panel degrades to reason-only blocked runs.
143
317
  }
144
318
  }
145
- registerRoutes(router, config, role) {
319
+ registerRoutes(router, config, durableTopology) {
146
320
  const apiBase = `${config.path}/api`;
147
- // Resolve the dashboard's read/control port lazily per request, branched by role (design §5/§8):
148
- // a store role adapts the engine (built by @adonis-agora/durable's provider); a store-less `tenant`
321
+ // Resolve the dashboard's read/control port lazily per request, branched by topology (design §5/§8):
322
+ // a store topology adapts the engine (built by @adonis-agora/durable's provider); a store-less `tenant`
149
323
  // pod resolves the DURABLE_RUN_GATEWAY token (a ProxyRunGateway) and adapts it — NO engine, NO store.
150
- const resolveEngine = () => dashboardEngineForRole(role, this.app.container, WorkflowEngine);
151
- const deps = async () => ({
152
- engine: await resolveEngine(),
153
- ...(config.redact ? { redact: config.redact } : {}),
154
- ...(this.metricsCollector ? { metrics: this.metricsCollector } : {}),
155
- });
156
- // Built-in `dashboardAuth` (Mode A `session` and/or Mode B `login`, opt-in). Registered ONLY when
157
- // configured, so omitting `dashboardAuth` leaves route registration byte-for-byte as it was. These
158
- // endpoints are public (behind NEITHER guard): they MINT the session the guard checks for.
159
- if (config.dashboardAuth) {
160
- this.registerAuthRoutes(router, config, config.dashboardAuth);
161
- }
324
+ const deps = this.consoleDeps(config, durableTopology);
162
325
  this.registerSpaRoutes(router, config, apiBase);
163
326
  // JSON API.
164
327
  const json = (handler) => {
@@ -170,7 +333,7 @@ export default class DashboardProvider {
170
333
  if (isMutatingMethod(ctx))
171
334
  this.audit(config, ctx);
172
335
  try {
173
- const result = await handler(await deps(), toApiRequest(ctx));
336
+ const result = await handler(await deps(ctx), toApiRequest(ctx));
174
337
  for (const [name, value] of Object.entries(result.headers ?? {})) {
175
338
  ctx.response.header(name, value);
176
339
  }
@@ -238,7 +401,7 @@ export default class DashboardProvider {
238
401
  if (!(await this.enforce(config, ctx, 'api')))
239
402
  return;
240
403
  try {
241
- const text = await metricsText(await deps());
404
+ const text = await metricsText(await deps(ctx));
242
405
  ctx.response.header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
243
406
  return ctx.response.status(200).send(text);
244
407
  }
@@ -263,6 +426,60 @@ export default class DashboardProvider {
263
426
  .post(`${apiBase}/schedules/:key/:action`, json(scheduleAction))
264
427
  .as('durable_dashboard.schedules.action');
265
428
  router.post(`${apiBase}/bulk/:action`, json(bulkAction)).as('durable_dashboard.bulk');
429
+ // Tenancy: which organizations this viewer may pick, and an audited record of a switch.
430
+ router
431
+ .get(`${apiBase}/tenants`, async (ctx) => {
432
+ if (!(await this.enforce(config, ctx, 'api')))
433
+ return;
434
+ ctx.response.header('cache-control', 'private, no-store');
435
+ try {
436
+ return ctx.response.status(200).json(await this.tenantPicker(config, ctx));
437
+ }
438
+ catch (error) {
439
+ void this.logServerError(error);
440
+ return ctx.response.status(500).json({ error: 'internal error' });
441
+ }
442
+ })
443
+ .as('durable_dashboard.tenants');
444
+ router
445
+ .post(`${apiBase}/tenants/:id/select`, async (ctx) => {
446
+ if (!(await this.enforce(config, ctx, 'api')))
447
+ return;
448
+ // The audit line (actor, `POST …/tenants/<id>/select`) is the record of the switch.
449
+ this.audit(config, ctx);
450
+ const scope = await this.consoleTenantScope(config, ctx);
451
+ const id = String(ctx.params.id);
452
+ const allowed = scope === undefined ||
453
+ id === '*' ||
454
+ scope.authorized === '*' ||
455
+ (Array.isArray(scope.authorized) && scope.authorized.includes(id));
456
+ return ctx.response
457
+ .status(allowed ? 200 : 404)
458
+ .json(allowed ? { tenant: id } : { error: 'not found' });
459
+ })
460
+ .as('durable_dashboard.tenants.select');
461
+ // The console switcher: the other Agora consoles this viewer may open, and a search across them.
462
+ router
463
+ .get(`${apiBase}/consoles`, async (ctx) => {
464
+ if (!(await this.enforce(config, ctx, 'api')))
465
+ return;
466
+ const registry = await readAgoraConsoles(this.app.container);
467
+ ctx.response.header('cache-control', 'private, no-store');
468
+ return ctx.response.status(200).json(await listVisibleConsoles(registry, CONSOLE_ID, ctx));
469
+ })
470
+ .as('durable_dashboard.consoles');
471
+ router
472
+ .get(`${apiBase}/consoles/search`, async (ctx) => {
473
+ if (!(await this.enforce(config, ctx, 'api')))
474
+ return;
475
+ const registry = await readAgoraConsoles(this.app.container);
476
+ const q = ctx.request.qs().q;
477
+ ctx.response.header('cache-control', 'private, no-store');
478
+ return ctx.response
479
+ .status(200)
480
+ .json(await searchConsoles(registry, CONSOLE_ID, ctx, typeof q === 'string' ? q : ''));
481
+ })
482
+ .as('durable_dashboard.consoles.search');
266
483
  router
267
484
  .get(`${apiBase}/health`, json((d) => health(d)))
268
485
  .as('durable_dashboard.health');
@@ -276,8 +493,8 @@ export default class DashboardProvider {
276
493
  // Only a `TenantConfig` carries a `partition` (its `<name>@<tenant>` routing suffix, spec
277
494
  // §6.1) — the field this dashboard's `DurableTopology.tenant` badge actually names.
278
495
  const durableConfig = this.app.config.get('durable', {});
279
- const tenant = durableConfig.role === 'tenant' ? durableConfig.partition : undefined;
280
- const result = topology(role, tenant);
496
+ const tenant = durableConfig.topology === 'tenant' ? durableConfig.partition : undefined;
497
+ const result = topology(durableTopology, tenant);
281
498
  return ctx.response.status(result.status).json(result.body);
282
499
  })
283
500
  .as('durable_dashboard.topology');
@@ -288,7 +505,11 @@ export default class DashboardProvider {
288
505
  if (!(await this.enforce(config, ctx, 'api')))
289
506
  return;
290
507
  const id = ctx.params.id;
291
- const engine = await resolveEngine();
508
+ const { engine } = await deps(ctx);
509
+ // A run outside the viewer's tenants streams nothing: answer like an unknown run.
510
+ if ((await engine.getRun(id)) === null && this.tenancyOn()) {
511
+ return ctx.response.status(404).json({ error: `run ${id} not found` });
512
+ }
292
513
  await this.streamRun(ctx, engine, id);
293
514
  })
294
515
  .as('durable_dashboard.runs.stream');
@@ -299,7 +520,7 @@ export default class DashboardProvider {
299
520
  if (!(await this.enforce(config, ctx, 'api')))
300
521
  return;
301
522
  try {
302
- const result = await compat(await this.compatSource(await resolveEngine()));
523
+ const result = await compat(await this.compatSource((await deps(ctx)).engine));
303
524
  return ctx.response.status(result.status).json(result.body);
304
525
  }
305
526
  catch (error) {
@@ -309,6 +530,22 @@ export default class DashboardProvider {
309
530
  })
310
531
  .as('durable_dashboard.compat');
311
532
  }
533
+ /**
534
+ * The console-shell fields of the page's data block: the switcher, ⌘K search (whenever a console
535
+ * registry exists — this console registers its own search), the signed-in viewer and the
536
+ * sign-out (the host's option, else the one an account console announced on the registry).
537
+ */
538
+ async shellFields(config, ctx, apiBase) {
539
+ const registry = await readAgoraConsoles(this.app.container);
540
+ const viewer = await resolveViewer(ctx, config.viewer);
541
+ const signOut = resolveSignOut(ctx, config.signOut, registry);
542
+ return {
543
+ switcher: { endpoint: `${apiBase}/consoles`, others: otherConsoles(registry, CONSOLE_ID) },
544
+ ...(registry ? { search: { endpoint: `${apiBase}/consoles/search` } } : {}),
545
+ ...(viewer ? { viewer } : {}),
546
+ ...(signOut ? { signOut } : {}),
547
+ };
548
+ }
312
549
  /** Mount the React SPA at `config.path` plus its hashed asset bundle. */
313
550
  registerSpaRoutes(router, config, apiBase) {
314
551
  const indexPath = config.path === '' ? '/' : config.path;
@@ -328,7 +565,7 @@ export default class DashboardProvider {
328
565
  return ctx.response
329
566
  .header('content-type', 'text/html; charset=utf-8')
330
567
  .header('cache-control', 'no-store, must-revalidate')
331
- .send(renderIndexHtml(html, config.path, apiBase));
568
+ .send(renderIndexHtml(html, config.path, apiBase, await this.shellFields(config, ctx, apiBase)));
332
569
  })
333
570
  .as('durable_dashboard.index');
334
571
  router
@@ -424,131 +661,24 @@ export default class DashboardProvider {
424
661
  return null;
425
662
  }
426
663
  }
427
- /**
428
- * Mount the built-in `dashboardAuth` endpoints under `basePath`. All are public (no guard): they
429
- * create/destroy the session the {@link enforce} guard checks for.
430
- *
431
- * - `GET <base>/login` -> the server-rendered login page (Mode B only).
432
- * - `POST <base>/login` -> verifies credentials via the host `login` hook and mints the cookie.
433
- * - `POST <base>/session` -> Mode A: verifies the HOST APP's own auth (off the raw request) via the
434
- * `session` hook and mints the cookie. No body — the hook reads whatever it needs off `request`.
435
- * `204` on success, uniform `401` on denial. Mirrors `@dudousxd/nestjs-durable-dashboard`'s
436
- * `DurableAuthController.session` and `client/console-session.ts`'s `mintDurableConsoleSession`.
437
- * - `GET <base>/logout` -> clears the cookie and redirects to the login page (Mode B) or the
438
- * dashboard root (Mode A only). A plain `GET` (idempotent, only ever destroys the caller's own
439
- * session) so a simple `<a href>` works.
440
- */
441
- registerAuthRoutes(router, config, auth) {
442
- const loginPath = `${config.path}/login`;
443
- const sessionPath = `${config.path}/session`;
444
- const logoutPath = `${config.path}/logout`;
445
- if (auth.login) {
446
- router
447
- .get(loginPath, async (ctx) => {
448
- ctx.response.header('content-type', 'text/html; charset=utf-8');
449
- ctx.response.header('cache-control', 'no-store, must-revalidate');
450
- const qs = ctx.request.qs();
451
- const nonce = cspNonce(ctx);
452
- return ctx.response.send(renderLoginPage(config.path, {
453
- ...(nonce !== undefined ? { nonce } : {}),
454
- error: qs.error !== undefined,
455
- returnTo: qs.returnTo,
456
- }));
457
- })
458
- .as('durable_dashboard.login.page');
459
- // Two callers: the page's own `fetch` (JSON in, JSON out — `{ redirectTo }` / `{ error }`)
460
- // and, with JavaScript off or its inline script dropped by a CSP, the same page as a classic
461
- // form post (form-encoded in, a redirect out: to `returnTo` on success, back to the login
462
- // page with `?error` on failure). Same hook, same cookie, same uniform failure either way.
463
- router
464
- .post(loginPath, async (ctx) => {
465
- if (this.loginRateLimited(ctx))
466
- return;
467
- const body = ctx.request.body();
468
- const outcome = await performLogin(auth, body, config.path);
469
- const form = isFormPost(ctx);
470
- const backToLogin = () => ctx.response
471
- .redirect()
472
- .withQs({
473
- error: '1',
474
- returnTo: sanitizeReturnTo(body?.returnTo, config.path || '/'),
475
- })
476
- .toPath(loginPath);
477
- if (outcome.kind === 'bad-request') {
478
- if (form)
479
- return backToLogin();
480
- return ctx.response.status(400).json({ error: outcome.message });
481
- }
482
- if (outcome.kind === 'unauthorized') {
483
- void this.warnHookThrow(outcome.hookError).catch(() => undefined);
484
- if (form)
485
- return backToLogin();
486
- return ctx.response.status(401).json({ error: outcome.message });
487
- }
488
- this.writeSessionCookie(ctx, auth, outcome.cookieValue);
489
- if (form)
490
- return ctx.response.redirect().toPath(outcome.redirectTo);
491
- return ctx.response.status(200).json({ redirectTo: outcome.redirectTo });
492
- })
493
- .as('durable_dashboard.login.submit');
494
- }
495
- if (auth.session) {
496
- router
497
- .post(sessionPath, async (ctx) => {
498
- if (this.loginRateLimited(ctx))
499
- return;
500
- const outcome = await performSession(auth, ctx.request.request);
501
- if (outcome.kind === 'unauthorized') {
502
- void this.warnHookThrow(outcome.hookError).catch(() => undefined);
503
- return ctx.response.status(401).json({ error: 'unauthorized' });
504
- }
505
- this.writeSessionCookie(ctx, auth, outcome.cookieValue);
506
- return ctx.response.status(204).send('');
507
- })
508
- .as('durable_dashboard.session.mint');
509
- }
510
- const logout = async (ctx) => {
511
- ctx.response.clearCookie(SESSION_COOKIE_NAME, { path: '/' });
512
- return ctx.response.redirect().toPath(auth.login ? loginPath : config.path || '/');
513
- };
514
- router.get(logoutPath, logout).as('durable_dashboard.logout');
515
- // POST too: the CSRF-proof spelling for clients that prefer it (the GET stays for plain <a>
516
- // links — it only ever destroys the caller's own session, so a forced logout is an annoyance,
517
- // not a compromise).
518
- router.post(logoutPath, logout).as('durable_dashboard.logout.post');
519
- }
520
664
  /** Record one mutating console action (see DashboardAuditEntry). Never throws into the request. */
521
665
  audit(config, ctx) {
522
666
  try {
523
- const auth = config.dashboardAuth;
524
- const session = auth ? readSession(auth, this.readSessionCookie(ctx)) : null;
525
- const fromToken = ctx.request.header('authorization') !== undefined ||
526
- ctx.request.header('x-durable-token') !== undefined;
527
- const actor = session?.name ?? session?.sub ?? (fromToken ? 'token' : 'anonymous');
528
- const entry = {
667
+ this.recordAudit(config, {
529
668
  at: new Date(),
530
- actor,
669
+ actor: auditActor(ctx),
531
670
  method: ctx.request.method().toUpperCase(),
532
671
  path: ctx.request.url(false),
533
- };
534
- if (config.audit) {
535
- // Typed `void`, but an async hook is assignable to it: catch its rejection too.
536
- const pending = config.audit(entry);
537
- if (pending instanceof Promise)
538
- pending.catch(() => undefined);
539
- return;
540
- }
541
- void this.app.container
542
- .make('logger')
543
- .then((logger) => {
544
- logger.info(`[durable_dashboard] audit actor=${entry.actor} ${entry.method} ${entry.path}`);
545
- })
546
- .catch(() => undefined);
672
+ });
547
673
  }
548
674
  catch {
549
675
  /* auditing must never break the action */
550
676
  }
551
677
  }
678
+ /** Hand one audit entry to the host's sink, or the default logger line. Never throws. */
679
+ recordAudit(config, entry) {
680
+ recordConsoleAudit(config.audit, () => this.app.container.make('logger'), entry);
681
+ }
552
682
  /** Route a handler 500's detail to the app logger (the client only ever sees a generic body). */
553
683
  async logServerError(error) {
554
684
  try {
@@ -567,95 +697,67 @@ export default class DashboardProvider {
567
697
  const logger = await this.app.container.make('logger');
568
698
  logger.warn(`[durable_dashboard] authorize hook threw; treating as denial (403). ${message}`);
569
699
  }
570
- async warnHookThrow(hookError) {
571
- if (hookError === undefined || this.warnedOnHookThrow)
572
- return;
573
- this.warnedOnHookThrow = true;
574
- const message = hookError instanceof Error ? hookError.message : String(hookError);
575
- const logger = await this.app.container.make('logger');
576
- logger.warn(`dashboardAuth login/session hook threw; treating as denial. ${message}`);
700
+ /** The once-per-boot warning for a console with no `authorize` (open in dev, 403 in production). */
701
+ async warnNoAuthorize() {
702
+ try {
703
+ const logger = await this.app.container.make('logger');
704
+ logger.warn(NO_AUTHORIZE_WARNING);
705
+ }
706
+ catch {
707
+ console.warn(NO_AUTHORIZE_WARNING);
708
+ }
577
709
  }
578
710
  /**
579
- * Run the guards for a dashboard resource. Composes the existing `authorize` hook (bearer
580
- * token/custom) with the optional `dashboardAuth` session guard — BOTH must pass:
711
+ * Run `authorize` for a dashboard resource. On a denial:
581
712
  *
582
- * 1. `authorize` denies -> the built-in (or host-customised) access-denied PAGE for a `page`
583
- * request, JSON for an `api` request. `reason: 'unauthenticated'` is a `401`
584
- * (`{ error: 'unauthorized' }`); `false`, `{ allowed: false }`, `reason: 'forbidden'` or a
585
- * throwing hook is a `403` (`{ error: 'forbidden' }`). A hook that already answered the request
586
- * (a redirect, a body) keeps its answer.
587
- * 2. `dashboardAuth` configured AND no valid session -> for a `page` request, redirect `302` to
588
- * the login page (Mode B) carrying a sanitized `returnTo`, or the `401` "open this console
589
- * from your app" page (Mode A only, no login page to redirect to); for an `api` request, a
590
- * plain `401 { error: 'unauthorized', auth: { modes } }` — the `modes` list lets the SPA's
591
- * client (`durable-client.ts`) decide whether to offer the login page.
713
+ * - `{ redirectTo }`: a `page` request is redirected there (`302`); an `api` request gets
714
+ * `401 { error: 'unauthenticated', redirectTo }`;
715
+ * - `reason: 'unauthenticated'`: `401` (the access-denied page, or `{ error: 'unauthenticated' }`);
716
+ * - anything else (`false`, `{ allowed: false }`, a throw): `403` (the access-denied page, or
717
+ * `{ error: 'forbidden' }`).
592
718
  *
593
- * When `dashboardAuth` is unconfigured only step 1 runs.
719
+ * A hook that already answered the request (a redirect, a body) keeps its answer.
594
720
  * Returns `false` (and has already written the response) when the request must short-circuit.
595
721
  */
596
722
  async enforce(config, ctx, mode) {
597
- // CSRF defense-in-depth over the Lax session cookie: a browser-originated CROSS-SITE mutating
598
- // request is rejected outright, whatever guard would have passed it. The mutating endpoints
599
- // need no body and read flags from the query string, so without this a malicious page could
600
- // fire them as SIMPLE cross-origin POSTs (no preflight) against a console the operator's
601
- // browser is authenticated to. Non-browser clients send neither header and pass.
723
+ // CSRF defense-in-depth: a browser-originated CROSS-SITE mutating request is rejected outright,
724
+ // whatever `authorize` would say. The mutating endpoints need no body and read flags from the
725
+ // query string, so without this a malicious page could fire them as SIMPLE cross-origin POSTs
726
+ // (no preflight) against a console the operator's browser is signed in to. Non-browser clients
727
+ // send neither header and pass.
602
728
  if (mode === 'api' && isMutatingMethod(ctx) && crossSiteRequest(ctx)) {
603
729
  ctx.response.status(403).json({ error: 'cross-site request rejected' });
604
730
  return false;
605
731
  }
606
- // Never test the raw result: a decision OBJECT is truthy, so `{ allowed: false }` would pass a
607
- // `!result` check. A throwing hook is a denial (403), never a 500 and never a pass.
608
- let decision;
609
- try {
610
- decision = normalizeAuthorizeResult(await config.authorize(ctx));
611
- }
612
- catch (error) {
613
- void this.warnAuthorizeThrow(error).catch(() => undefined);
614
- decision = { allowed: false, reason: 'forbidden' };
615
- }
616
- if (!decision.allowed) {
617
- const unauthenticated = decision.reason === 'unauthenticated';
618
- if (mode === 'page') {
619
- await this.denyPage(ctx, config, unauthenticated
620
- ? { status: 401, reason: 'unauthenticated' }
621
- : { status: 403, reason: 'forbidden' });
622
- }
623
- else if (!responseAnswered(ctx)) {
624
- ctx.response
625
- .status(unauthenticated ? 401 : 403)
626
- .json({ error: unauthenticated ? 'unauthorized' : 'forbidden' });
627
- }
628
- return false;
629
- }
630
- const auth = config.dashboardAuth;
631
- if (!auth)
732
+ // The resolved gate never throws: a throwing `authorize` comes back as a 403 carrying the error.
733
+ const decision = await config.authorize(ctx);
734
+ this.decisions.set(ctx, decision);
735
+ if (decision.allowed)
632
736
  return true;
633
- const session = readSession(auth, this.readSessionCookie(ctx));
634
- if (session) {
635
- // Role convention: a session WITHOUT roles keeps full access (back-compat — most host hooks
636
- // never set them); a session WITH roles must carry `operator` (or `admin`) to mutate.
637
- // Previously every authenticated principal — including a read-only support login — could
638
- // bulk-cancel with compensation or fix-and-replay.
639
- if (mode === 'api' && isMutatingMethod(ctx) && !sessionCanMutate(session)) {
640
- ctx.response
641
- .status(403)
642
- .json({ error: "this session's roles do not include 'operator' (or 'admin')" });
643
- return false;
644
- }
645
- return true;
646
- }
737
+ if (decision.error !== undefined)
738
+ void this.warnAuthorizeThrow(decision.error).catch(() => undefined);
739
+ const unauthenticated = decision.reason === 'unauthenticated';
647
740
  if (mode === 'page') {
648
- if (auth.login) {
649
- const returnTo = ctx.request.url(true);
650
- ctx.response.redirect().withQs('returnTo', returnTo).toPath(`${config.path}/login`);
741
+ if (decision.redirectTo !== undefined && !responseAnswered(ctx)) {
742
+ ctx.response.redirect().toPath(decision.redirectTo);
651
743
  return false;
652
744
  }
653
- // Mode A only: there's no login page to send the browser to — this deployment expects the
654
- // host app to mint a session via `POST <path>/session` before ever navigating here.
655
- await this.denyPage(ctx, config, { status: 401, reason: 'session-required' });
745
+ await this.denyPage(ctx, config, unauthenticated
746
+ ? { status: 401, reason: 'unauthenticated' }
747
+ : { status: 403, reason: 'forbidden' });
748
+ return false;
749
+ }
750
+ if (responseAnswered(ctx))
656
751
  return false;
752
+ if (!unauthenticated) {
753
+ ctx.response.status(403).json({ error: 'forbidden' });
754
+ }
755
+ else if (decision.redirectTo !== undefined) {
756
+ ctx.response.status(401).json({ error: 'unauthenticated', redirectTo: decision.redirectTo });
757
+ }
758
+ else {
759
+ ctx.response.status(401).json({ error: 'unauthenticated' });
657
760
  }
658
- ctx.response.status(401).json({ error: 'unauthorized', auth: { modes: auth.modes } });
659
761
  return false;
660
762
  }
661
763
  /**
@@ -672,7 +774,6 @@ export default class DashboardProvider {
672
774
  const info = {
673
775
  ...denial,
674
776
  basePath: config.path,
675
- ...(config.dashboardAuth?.login ? { loginHref: `${config.path}/login` } : {}),
676
777
  ...(nonce !== undefined ? { nonce } : {}),
677
778
  };
678
779
  const html = await resolveAccessDeniedPage(info, config.accessDenied, ctx, answered);
@@ -684,22 +785,6 @@ export default class DashboardProvider {
684
785
  .header('cache-control', 'no-store, must-revalidate')
685
786
  .send(html);
686
787
  }
687
- readSessionCookie(ctx) {
688
- const value = ctx.request.plainCookie(SESSION_COOKIE_NAME, undefined, false);
689
- return typeof value === 'string' && value !== '' ? value : undefined;
690
- }
691
- writeSessionCookie(ctx, auth, value) {
692
- ctx.response.plainCookie(SESSION_COOKIE_NAME, value, {
693
- httpOnly: true,
694
- sameSite: 'lax',
695
- // Forced on in production even when `request.secure()` reads false (a TLS-terminating proxy
696
- // without AdonisJS trustProxy) — exactly the deployments that most need the Secure flag.
697
- secure: isProduction() || ctx.request.secure(),
698
- path: '/',
699
- maxAge: Math.floor(auth.ttlMs / 1000),
700
- encode: false,
701
- });
702
- }
703
788
  async shutdown() {
704
789
  this.detachDiagnostics?.();
705
790
  this.detachDiagnostics = null;
@@ -707,12 +792,6 @@ export default class DashboardProvider {
707
792
  this.metricsCollector = null;
708
793
  }
709
794
  }
710
- /**
711
- * Whether something already answered this request: a redirect (`location` header — the signal the
712
- * `authorize` contract has always honoured) or a body queued on the response. The body check reads
713
- * AdonisJS's `response.hasLazyBody` structurally so a plain-object `ctx` double in a unit test
714
- * (which has neither) still works.
715
- */
716
795
  /** Whether this request can change state (everything but GET/HEAD/OPTIONS). Exported for tests. */
717
796
  export function isMutatingMethod(ctx) {
718
797
  const method = ctx.request.method().toUpperCase();
@@ -739,15 +818,12 @@ export function crossSiteRequest(ctx) {
739
818
  return true; // an unparseable Origin is not a browser being honest — reject
740
819
  }
741
820
  }
742
- /** Role convention (see enforce): no roles = full access; with roles, mutating needs operator/admin. */
743
- export function sessionCanMutate(session) {
744
- if (session.roles.length === 0)
745
- return true;
746
- return session.roles.some((r) => {
747
- const role = r.toLowerCase();
748
- return role === 'operator' || role === 'admin';
749
- });
750
- }
821
+ /**
822
+ * Whether something already answered this request: a redirect (`location` header — the signal the
823
+ * `authorize` contract has always honoured) or a body queued on the response. The body check reads
824
+ * AdonisJS's `response.hasLazyBody` structurally so a plain-object `ctx` double in a unit test
825
+ * (which has neither) still works.
826
+ */
751
827
  function responseAnswered(ctx) {
752
828
  if (ctx.response.getHeader('location'))
753
829
  return true;
@@ -764,21 +840,56 @@ function cspNonce(ctx) {
764
840
  return typeof nonce === 'string' && nonce !== '' ? nonce : undefined;
765
841
  }
766
842
  /**
767
- * Whether a login `POST` came from a classic HTML form submit (form-encoded — JavaScript off, or
768
- * the page's inline script dropped by a CSP) rather than the page's own JSON `fetch`. Decides
769
- * whether the reply is a redirect (a browser navigating) or JSON (a script awaiting it).
843
+ * Who a mutating console action is attributed to: the signed-in user (`ctx.auth.user`'s `email`,
844
+ * `name` or `id`, read structurally — this package does not depend on `@adonisjs/auth`), `'bearer'`
845
+ * for a caller with an `Authorization` header and no user, else `'anonymous'`.
770
846
  */
771
- function isFormPost(ctx) {
772
- const type = ctx.request.header('content-type') ?? '';
773
- return type.includes('application/x-www-form-urlencoded') || type.includes('multipart/form-data');
847
+ export function auditActor(ctx) {
848
+ const user = ctx.auth?.user;
849
+ if (user) {
850
+ for (const key of ['email', 'name', 'id']) {
851
+ const value = user[key];
852
+ if (typeof value === 'string' && value !== '')
853
+ return value;
854
+ if (typeof value === 'number')
855
+ return String(value);
856
+ }
857
+ }
858
+ return ctx.request.header('authorization') !== undefined ? 'bearer' : 'anonymous';
774
859
  }
775
860
  /** Adapt an AdonisJS `HttpContext` to the framework-light {@link ApiRequest}. */
776
861
  function toApiRequest(ctx) {
777
862
  return {
778
863
  params: ctx.params,
779
- query: ctx.request.qs(),
864
+ // `tenant` picks the console's organization (see consoleTenantScope); it is not a run filter.
865
+ query: withoutTenantParam(ctx.request.qs()),
780
866
  body: ctx.request.body(),
781
867
  headers: { 'if-none-match': ctx.request.header('if-none-match') },
782
868
  };
783
869
  }
870
+ function withoutTenantParam(query) {
871
+ if (!('tenant' in query))
872
+ return query;
873
+ const { tenant: _tenant, ...rest } = query;
874
+ return rest;
875
+ }
876
+ /**
877
+ * Display labels for tenant ids: the viewer's own tenant by its slug, the rest through context's
878
+ * label resolver (`tenancy.label` in config/context.ts, or the one authkit installs), else the id.
879
+ */
880
+ async function tenantLabelsFor(ids, own) {
881
+ const labels = {};
882
+ const slot = tenancySlot();
883
+ if (slot?.tenantLabels && ids.length > 0) {
884
+ try {
885
+ Object.assign(labels, await slot.tenantLabels(ids));
886
+ }
887
+ catch {
888
+ /* labels are cosmetic: fall back to ids */
889
+ }
890
+ }
891
+ if (own?.slug && labels[own.id] === undefined)
892
+ labels[own.id] = own.slug;
893
+ return labels;
894
+ }
784
895
  //# sourceMappingURL=dashboard_provider.js.map