ADR-093: One tool gate, in the loop — not in the controller
- Status
-
Accepted
- Date
-
2026-07-20
- Authors
-
Netresearch DTT GmbH
Context
The tool subsystem enforces its policy in several places, and an audit found that three of those places disagreed with what the documentation claimed.
The per-configuration gate was in the wrong layer. A configuration's
allowed_ and its skills' declared allow-list were applied by
Tool, not by the loop. That was defensible while the
playground was the only entry point. It stopped being defensible when
Tool was published in 0.23.0 so downstream extensions
could drive the loop: every such consumer bypassed the configuration's own
restriction entirely and received the full globally-enabled set.
Two schema tools disagreed on what may be read. get_ routed its
decision through Table, whose sensitive-table denylist
holds for administrators too. get_ checked tables_ directly —
and Backend returns true for every table for an
admin, so get_ described tx_ and the nr_
tables to any admin-run loop, vault key references included.
A documented egress invariant was false. Egress stated that
a group without an entry in its map "cannot egress anywhere" and listed rag
among the groups that "read local state only". Meanwhile the RAG tools reach
Solr, which assembles a scheme:// URL from the site
configuration and hands it to an HTTP client without ever consulting the policy.
Decision
The loop is the chokepoint. Tool now
applies the per-configuration gate itself, alongside the global enablement
intersection and the fail-closed admin filter it already applied. The caller's
tool list is a request; the configuration is the grant. resume runs
the same resolution, so a run suspended before a configuration was tightened is
re-checked at approval time.
Tool, run and resume are unchanged —
both already receive the Llm. The playground keeps passing the
admin's checkbox selection and simply stops applying the gate a second time, so
its observable behaviour is identical.
One table policy. get_ routes both its list and its describe path
through Table. A denied table returns the
same neutral Unknown TCA table. string as an unknown one, so the tool never
confirms a table's existence.
The egress map tells the truth. A new scope,
Tool, expresses an operator-declared service
host that is not a site base — OWN_ cannot describe a Solr host, so
without it the map could only be satisfied by misdeclaring the group. rag
maps to it, and Solr validates its assembled URL through
Egress: http(s) only, no userinfo,
exact host:port match against the configured host. A denial returns null, the
method's established "not configured" path, which the retrieval service treats
as an unavailable backend and skips.
Consequences
- The published ``ToolLoopServiceInterface`` contract narrows. A consumer
that previously received tools outside its configuration's
allowed_now receives fewer. No shipped behaviour changes — the only production consumer already applied the intersection — but this is a deliberate tightening and belongs in the release notes.tool_ groups get_no longer describes the extension's own ortca nr_'s tables, to anyone, including administrators. That is the point.vault - The RAG egress gate is an audit and consistency gate, not a new confidentiality boundary. The Solr host was always operator-supplied from the site configuration and never model-supplied; what changes is that the invariant this class documents is now checkable in code instead of merely asserted. Overselling it would be dishonest.
- Both new collaborators are optional constructor arguments so the existing lean test wiring keeps working. In production the container injects them; a unit test that omits them exercises the pre-existing gates only.
Classes/now referencesService/ Retrieval Classes/. That crosses a horizontal seam ADR-090 names but does not yet enforce. The alternative — duplicating URL validation inside the retrieval module — would be a second policy, which is the very failure this ADR is correcting.Service/ Tool - Still open, and deliberately not addressed here: tools carry no data classification and providers no trust zone, so nothing yet prevents a diagnostics tool's output from egressing to an external provider. That is the next step and needs its own ADR.