|
29 | 29 | /** |
30 | 30 | * Backend module registration for nr_llm. |
31 | 31 | * |
32 | | - * Structure: Main module under 'tools', sub-modules as children of main module. |
33 | | - * Sub-modules only appear in docheader dropdown, not in main navigation. |
| 32 | + * Structure (ADR-119, decided in #812): |
34 | 33 | * |
35 | | - * Uses 'tools' as parent for v13+v14 compatibility: |
36 | | - * - v13: 'tools' exists natively as the admin tools group |
37 | | - * - v14: 'tools' is an alias for the new 'admin' group |
| 34 | + * netresearch_ai top-level section, shared with the sibling |
| 35 | + * extensions; no access check of its own |
| 36 | + * ├── nrllm_aitasks editor surface (access => user) |
| 37 | + * ├── nrllm_overview landing page (access => admin) |
| 38 | + * ├── nrllm_setup providers, models, configurations, use-case |
| 39 | + * ├── nrllm_authoring tasks, skills, snippets |
| 40 | + * └── nrllm_operation tools, MCP, playground, runs, analytics |
38 | 41 | * |
39 | | - * Pattern follows TYPO3 Styleguide extension: |
40 | | - * - Main module identifier without prefix (e.g., 'nrllm' not 'tools_nrllm') |
41 | | - * - Child modules with parent as prefix (e.g., 'nrllm_providers') |
42 | | - * - Nested paths under main module path |
| 42 | + * The section replaces 'tools' as the top-level parent; the depth is unchanged |
| 43 | + * (a section held one container holding fourteen entries before, and now holds |
| 44 | + * five entries holding their own). |
43 | 45 | * |
44 | | - * v13 compatibility: 'nrllm_overview' is registered as first submodule so that |
45 | | - * v13 (which redirects to the first submodule) shows the overview page. |
46 | | - * v14 uses 'showSubmoduleOverview' on the parent module for the same effect. |
| 46 | + * WHY A SECTION AND NOT A CONTAINER UNDER ADMINISTRATION. The module menu drops |
| 47 | + * every top-level module whose own access check FAILS, together with all its |
| 48 | + * children (ADR-131). Under the admin-only 'nrllm' container an editor surface |
| 49 | + * was therefore invisible, and 'nrllm_aitasks' had to be parented to 'web' to |
| 50 | + * be reachable at all. Every further editor surface would have landed there for |
| 51 | + * the same reason, one flat entry at a time. A section carries no 'access' key |
| 52 | + * of its own — exactly like the core's own sections — so it never filters; its |
| 53 | + * children filter individually, and admin-only and editor-facing modules can |
| 54 | + * finally live in one place. |
| 55 | + * |
| 56 | + * WHY THE IDENTIFIER IS VENDOR-SCOPED. Module identifiers merge last-package- |
| 57 | + * wins. A bare 'ai' would be a shared namespace with no owner: the label and |
| 58 | + * icon would depend on package load order, and removing the owning extension |
| 59 | + * would strip the routes of any foreign submodule parented to it. |
| 60 | + * |
| 61 | + * OLD ROUTES. 'nrllm' is no longer a registered identifier, so 'nrllm_overview' |
| 62 | + * carries it as an alias — an alias is shadowed by a real module of the same |
| 63 | + * name, which is why the container had to go first. Backend shortcuts store the |
| 64 | + * module identifier, so they resolve through that alias. ModuleFactory also |
| 65 | + * rewrites 'position' references through aliases, so a foreign module anchored |
| 66 | + * with ['after' => 'nrllm'] keeps its place without changing. |
| 67 | + * |
| 68 | + * Submodule identifiers and explicit paths are unchanged on purpose: a regroup |
| 69 | + * that also renamed the leaves would break every bookmark for no gain. |
| 70 | + * |
| 71 | + * v13 compatibility: each container registers 'nrllm_*' children and carries |
| 72 | + * both 'dependsOnSubmodules' (v13 redirects to the first submodule) and |
| 73 | + * 'showSubmoduleOverview' (v14 renders an overview instead). |
47 | 74 | */ |
48 | 75 | return [ |
49 | | - // Main dashboard module (parent container) |
50 | | - 'nrllm' => [ |
51 | | - 'parent' => 'tools', |
52 | | - 'position' => ['after' => 'styleguide'], |
53 | | - 'access' => 'admin', |
| 76 | + // The shared top-level section. No 'parent', no 'path', no 'access' and no |
| 77 | + // 'controllerActions' — that is the shape of a section rather than a module, |
| 78 | + // and it is what the core's own sections look like. The absent access check |
| 79 | + // is load-bearing, not an omission: see the header. |
| 80 | + // |
| 81 | + // Positioned after 'media' rather than at the end with the admin sections, |
| 82 | + // because the audience that makes the section necessary is editors. |
| 83 | + 'netresearch_ai' => [ |
| 84 | + 'position' => ['after' => 'media'], |
54 | 85 | 'iconIdentifier' => 'module-nrllm', |
55 | | - 'path' => '/module/nrllm', |
56 | | - 'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod.xlf', |
| 86 | + 'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_section.xlf', |
| 87 | + ], |
| 88 | + // Setup: what has to exist before anything can run. |
| 89 | + 'nrllm_setup' => [ |
| 90 | + 'parent' => 'netresearch_ai', |
| 91 | + 'access' => 'admin', |
| 92 | + 'iconIdentifier' => 'module-nrllm-provider', |
| 93 | + 'path' => '/module/nrllm/setup', |
| 94 | + 'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_setup.xlf', |
57 | 95 | 'extensionName' => 'NrLlm', |
58 | 96 | 'appearance' => [ |
59 | 97 | 'dependsOnSubmodules' => true, |
60 | 98 | ], |
61 | | - // v14+: Show overview page for parent module |
62 | 99 | 'showSubmoduleOverview' => true, |
63 | | - 'controllerActions' => [ |
64 | | - LlmModuleController::class => [ |
65 | | - 'index', |
66 | | - 'test', |
67 | | - 'executeTest', |
68 | | - 'governance', |
69 | | - 'help', |
70 | | - ], |
| 100 | + ], |
| 101 | + // Authoring: what the models are asked to do. |
| 102 | + 'nrllm_authoring' => [ |
| 103 | + 'parent' => 'netresearch_ai', |
| 104 | + 'access' => 'admin', |
| 105 | + 'iconIdentifier' => 'module-nrllm-snippet', |
| 106 | + 'path' => '/module/nrllm/authoring', |
| 107 | + 'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_authoring.xlf', |
| 108 | + 'extensionName' => 'NrLlm', |
| 109 | + 'appearance' => [ |
| 110 | + 'dependsOnSubmodules' => true, |
| 111 | + ], |
| 112 | + 'showSubmoduleOverview' => true, |
| 113 | + ], |
| 114 | + // Operation: what is running, and what it cost. |
| 115 | + 'nrllm_operation' => [ |
| 116 | + 'parent' => 'netresearch_ai', |
| 117 | + 'access' => 'admin', |
| 118 | + 'iconIdentifier' => 'module-nrllm-runs', |
| 119 | + 'path' => '/module/nrllm/operation', |
| 120 | + 'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_operation.xlf', |
| 121 | + 'extensionName' => 'NrLlm', |
| 122 | + 'appearance' => [ |
| 123 | + 'dependsOnSubmodules' => true, |
71 | 124 | ], |
| 125 | + 'showSubmoduleOverview' => true, |
72 | 126 | ], |
73 | | - // Overview submodule - v13 compatibility |
74 | | - // In v13, dependsOnSubmodules redirects to the first submodule. |
75 | | - // This ensures the overview page is shown instead of providers. |
76 | | - // In v14, showSubmoduleOverview on the parent handles this natively. |
| 127 | + // The section's landing page, and the holder of the old container's |
| 128 | + // identifier. 'aliases' resolves backend shortcuts stored against 'nrllm' |
| 129 | + // and re-anchors foreign modules positioned ['after' => 'nrllm'] — both |
| 130 | + // only work because 'nrllm' is no longer registered as a real module, which |
| 131 | + // would shadow the alias. |
77 | 132 | 'nrllm_overview' => [ |
78 | | - 'parent' => 'nrllm', |
| 133 | + 'parent' => 'netresearch_ai', |
79 | 134 | 'position' => ['before' => '*'], |
| 135 | + 'aliases' => ['nrllm'], |
80 | 136 | 'access' => 'admin', |
81 | 137 | 'iconIdentifier' => 'module-nrllm', |
82 | 138 | 'path' => '/module/nrllm/overview', |
|
99 | 155 | // Provider management - child of main module |
100 | 156 | // Note: AJAX actions (toggleActive, testConnection) are registered via AjaxRoutes.php |
101 | 157 | 'nrllm_providers' => [ |
102 | | - 'parent' => 'nrllm', |
| 158 | + 'parent' => 'nrllm_setup', |
103 | 159 | 'access' => 'admin', |
104 | 160 | 'iconIdentifier' => 'module-nrllm-provider', |
105 | 161 | 'path' => '/module/nrllm/providers', |
|
114 | 170 | // Model management - child of main module |
115 | 171 | // Note: AJAX actions (toggleActive, setDefault, etc.) are registered via AjaxRoutes.php |
116 | 172 | 'nrllm_models' => [ |
117 | | - 'parent' => 'nrllm', |
| 173 | + 'parent' => 'nrllm_setup', |
118 | 174 | 'access' => 'admin', |
119 | 175 | 'iconIdentifier' => 'module-nrllm-model', |
120 | 176 | 'path' => '/module/nrllm/models', |
|
129 | 185 | // Configuration management - child of main module |
130 | 186 | // Note: AJAX actions (toggleActive, setDefault, testConfiguration) are registered via AjaxRoutes.php |
131 | 187 | 'nrllm_configurations' => [ |
132 | | - 'parent' => 'nrllm', |
| 188 | + 'parent' => 'nrllm_setup', |
133 | 189 | 'access' => 'admin', |
134 | 190 | 'iconIdentifier' => 'module-nrllm', |
135 | 191 | 'path' => '/module/nrllm/configurations', |
|
146 | 202 | // Task management - child of main module |
147 | 203 | // Note: new/edit/save/delete use FormEngine (record_edit route), AJAX actions via AjaxRoutes.php |
148 | 204 | 'nrllm_tasks' => [ |
149 | | - 'parent' => 'nrllm', |
| 205 | + 'parent' => 'nrllm_authoring', |
150 | 206 | 'access' => 'admin', |
151 | 207 | 'iconIdentifier' => 'module-nrllm-task', |
152 | 208 | 'path' => '/module/nrllm/tasks', |
|
170 | 226 | // Prompt snippet library - child of main module |
171 | 227 | // Note: new/edit/save/delete use FormEngine (record_edit route) |
172 | 228 | 'nrllm_snippets' => [ |
173 | | - 'parent' => 'nrllm', |
| 229 | + 'parent' => 'nrllm_authoring', |
174 | 230 | 'access' => 'admin', |
175 | 231 | 'iconIdentifier' => 'module-nrllm-snippet', |
176 | 232 | 'path' => '/module/nrllm/snippets', |
|
190 | 246 | // Shares the wizard's icon deliberately: the two are one entry path, and a |
191 | 247 | // second wizard-family glyph would suggest a second kind of thing. |
192 | 248 | 'nrllm_usecase' => [ |
193 | | - 'parent' => 'nrllm', |
| 249 | + 'parent' => 'nrllm_setup', |
194 | 250 | 'access' => 'admin', |
195 | 251 | 'iconIdentifier' => 'module-nrllm-wizard', |
196 | 252 | 'path' => '/module/nrllm/use-case', |
|
207 | 263 | // Setup wizard - child of main module |
208 | 264 | // Note: AJAX actions (detect, test, discover, generate, save) are registered via AjaxRoutes.php |
209 | 265 | 'nrllm_wizard' => [ |
210 | | - 'parent' => 'nrllm', |
| 266 | + 'parent' => 'nrllm_setup', |
211 | 267 | 'access' => 'admin', |
212 | 268 | 'iconIdentifier' => 'module-nrllm-wizard', |
213 | 269 | 'path' => '/module/nrllm/wizard', |
|
222 | 278 | // Skills management - child of main module |
223 | 279 | // Note: AJAX actions (sync, toggleSkill, setToken) are registered via AjaxRoutes.php |
224 | 280 | 'nrllm_skills' => [ |
225 | | - 'parent' => 'nrllm', |
| 281 | + 'parent' => 'nrllm_authoring', |
226 | 282 | 'access' => 'admin', |
227 | 283 | 'iconIdentifier' => 'module-nrllm-skill', |
228 | 284 | 'path' => '/module/nrllm/skills', |
|
240 | 296 | // (nrllm_tool_toggle) and additionally guards itself with |
241 | 297 | // RequiresBackendAdminTrait (ADR-037). |
242 | 298 | 'nrllm_tools' => [ |
243 | | - 'parent' => 'nrllm', |
| 299 | + 'parent' => 'nrllm_operation', |
244 | 300 | 'access' => 'admin', |
245 | 301 | 'iconIdentifier' => 'module-nrllm-tool', |
246 | 302 | 'path' => '/module/nrllm/tools', |
|
260 | 316 | // RequiresBackendAdminTrait because a backend route bypasses this |
261 | 317 | // module's access setting (ADR-037). |
262 | 318 | 'nrllm_mcp' => [ |
263 | | - 'parent' => 'nrllm', |
| 319 | + 'parent' => 'nrllm_operation', |
264 | 320 | 'access' => 'admin', |
265 | 321 | 'iconIdentifier' => 'module-nrllm-tool', |
266 | 322 | 'path' => '/module/nrllm/mcp', |
|
278 | 334 | // Note: the AJAX runAction is registered via AjaxRoutes.php (nrllm_tool_run) |
279 | 335 | // and additionally guards itself with RequiresBackendAdminTrait. |
280 | 336 | 'nrllm_playground' => [ |
281 | | - 'parent' => 'nrllm', |
| 337 | + 'parent' => 'nrllm_operation', |
282 | 338 | 'access' => 'admin', |
283 | 339 | 'iconIdentifier' => 'module-nrllm-tool', |
284 | 340 | 'path' => '/module/nrllm/playground', |
|
297 | 353 | // gates the three list/write actions, and `show` is additionally authorised |
298 | 354 | // per run by the runtime (AGENT_READ). |
299 | 355 | 'nrllm_runs' => [ |
300 | | - 'parent' => 'nrllm', |
| 356 | + 'parent' => 'nrllm_operation', |
301 | 357 | 'access' => 'admin', |
302 | 358 | 'iconIdentifier' => 'module-nrllm-runs', |
303 | 359 | 'path' => '/module/nrllm/runs', |
|
317 | 373 | ], |
318 | 374 | // Usage analytics dashboard - child of main module |
319 | 375 | 'nrllm_analytics' => [ |
320 | | - 'parent' => 'nrllm', |
| 376 | + 'parent' => 'nrllm_operation', |
321 | 377 | 'access' => 'admin', |
322 | 378 | 'iconIdentifier' => 'module-nrllm-analytics', |
323 | 379 | 'path' => '/module/nrllm/analytics', |
|
340 | 396 | // ticked in be_groups; the tasks_use/agent_approve grants are checked per |
341 | 397 | // action on top — the module switch alone never grants execution. |
342 | 398 | 'nrllm_aitasks' => [ |
343 | | - 'parent' => 'web', |
| 399 | + 'parent' => 'netresearch_ai', |
344 | 400 | 'access' => 'user', |
345 | 401 | 'iconIdentifier' => 'module-nrllm-task', |
346 | 402 | 'path' => '/module/web/nrllm-aitasks', |
|
0 commit comments