ADR-131: The editor-facing module
- Status
-
Accepted
- Date
-
2026-08-06
Context
ADR-130 shipped capability grants, and its own constraints
list said why they were not yet observable: every nr_llm module is
access => admin, so a gate administrators bypass is inert inside the
admin UI (ADR-117's third finding). The committed follow-up is a surface
non-admins can actually reach.
Decision
One new backend module, nrllm_aitasks ("AI Tasks"), containing exactly
what the editing role holds: run a prepared task, and decide agent runs
waiting for approval. Everything else — configuration, providers, models,
playground, wizards, analytics — stays admin-only.
Four structural choices, each forced by a verified platform constraint:
- Top-level in the ``web`` group, not a child of ``nrllm``. The module
menu drops every top-level module whose own access check fails; since
nrllmis admin-only, any child would be invisible to non-admins no matter its own access. Editors work inweb, so that is where their module lives. - ``access => 'user'``, never ``'user,group'``. TYPO3 v14 resolves the
access string through a gate registry that knows only
user,adminandsystemMaintainer— an unknown string denies everyone, administrators included.'user'means the module must be ticked in the be_groups module list; the ADR-130 grants are checked per action on top. Two switches, both required, both documented. - The approvals inbox is the SAME controller, not a copy.
Agent(stale-review digest, schema coercion, the exception map) is registered in both modules. Visibility is actor-scoped in one place: an admin or anRun Controller agent_approveholder sees every run, everyone else only their own (a new optionalbeUserfilter on the run queries). The list filter is a viewport — the write side stays independently authorised per run byAi.Actor Context:: may Act On Run () - The task surface is a NEW slim controller
(
Ai), because the admin execute form cannot be reused as-is: it renders FormEngine edit links for four tables an editor cannot touch, and it embeds the database record picker. The editor templates share theTask Controller data-task-executecontract, soTaskExecute.jsis reused unchanged (its element lookups are null-safe where the picker is absent).
What stays out, and why
- Tasks with the ``table`` input type are filtered from the editor
list (and their execute form redirects like an unknown uid, so a
guessed id leaks nothing). The record-picker endpoints read arbitrary
tables with only housekeeping exclusions — no
tables_selectcheck, no sensitive-table denylist — and stay admin-only (ADR-130). WiringTableinto the picker would also restrict administrators, which is its own decision, not a side effect of this module.Read Access Service - ``tasks_manage`` still does not exist. This module adds no management surface, so the grant would still have no enforcement point.
- "Own runs" for editors is mostly the approver's view. Task executions do not create agent runs (they are usage rows); agent runs are currently started from admin surfaces. The ownership filter matters the moment any non-admin path starts runs, and costs nothing now.
Consequences
- Both ADR-130 grants become observable:
tasks_usethrough the task list/run form,agent_approvethrough the shared approvals inbox (previously doubly unreachable for non-admins). - The inbox infobox no longer claims to be admin-only; it states the actual visibility rule for the current actor.
- Admin discovery: the overview's Tasks card links to the editor module.
- The gate primitive for HTML module actions
(
deny) complements the AJAX JSON variant from ADR-130.Without Grant Html ()