ADR 131 · Accepted
ADR-131: The editor-facing module
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 nrllm is admin-only, any child would be invisible to non-admins no matter its own access. Editors work in web, 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, admin and systemMaintainer — 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. AgentRunController (stale-review digest, schema coercion, the exception map) is registered in both modules. Visibility is actor-scoped in one place: an admin or an agent_approve holder sees every run, everyone else only their own (a new optional beUser filter on the run queries). The list filter is a viewport — the write side stays independently authorised per run by AiActorContext::mayActOnRun().
The task surface is a NEW slim controller (AiTaskController), 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 the data-task-execute contract, so TaskExecute.js is 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_select check, no sensitive-table denylist — and stay admin-only (ADR-130). Wiring TableReadAccessService into the picker would also restrict administrators, which is its own decision, not a side effect of this module.
``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_use through the task list/run form, agent_approve through 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 (denyWithoutGrantHtml()) complements the AJAX JSON variant from ADR-130.