Owner (2026-10-13): "the agent should have to go through the same API that the UIUX does. that way it is held to the same security boundaries etc". The design and owner decisions are in docs/DESIGN.md ("Owner decisions (2026-10-13)", "Tools through the UI API").
What changes
Every data tool runs a spec'd /qqq/v1 operation with the user's own session.UiApiGateway sends a loopback HTTP request carrying the sessionUUID cookie, so each call goes through the UI's authentication, permissions, record locks, validation, audit and error mapping.
Only MiddlewareVersionV1 spec'd operations can be called. Raw routes, including the assistant's own, are refused before any request, as is a call without a session.
LoopbackUiApiGateway is a stand-in for core's UiApiInvoker (QRun-IO/qqq#1128) with the same contract. It will be replaced by an adapter once a QQQ release has it.
navigate and search_help read no records, so they stay on the kernel.
Tools and their operations:
list_tables: GET /metaData
describe_table: GET /metaData/table/{t}
query_records and count_records: POST /table/{t}/query and /count
get_record: GET /table/{t}/{pk}
search_records: POST /search
render_widget: POST /widget/{w}
get_record_history: GET the record, then query audit_table, audit and audit_detail
new create_record, update_record and delete_record: POST, PATCH and DELETE on /table/...
Data access (assistant_data_access, a nav table): NONE, READ or READ_WRITE per table or process.
Sensitive tables (AI exposure NONE) are NONE and cannot be raised. Other tables default to READ, processes to NONE.
Data access only ever narrows what the user may do.
Writes need confirmation (policy default).
A write tool only creates an owner-locked assistant_pending_action, shown as a card (render pending_action).
POST /qqq/v1/assistant/actions/{id}/confirm runs it once, through the UI API, with the confirming request's session. .../reject runs nothing.
Policy:writesRequireConfirmation, allowedTools, maxRowsPerCall and redactPii.
Tool-call log: adds the UI API operation, HTTP status, audit link and pending action. Inputs are stored with secrets redacted. Pending actions are a child list on conversations.
Security tests (UiApiToolsSecurityTest, real QQQ application server)
Each case sends the UI's own request (a direct /qqq/v1 call with the user's cookie) next to the assistant's tool call:
Read denied: an analyst without blog_post.read gets 403 with the same message from query, get and count, both ways.
Write denied: an editor without insert or delete permission gets the UI's 403, even with READ_WRITE and confirmation off.
Data access only narrows:
NONE hides a table the user can read, and no request is made.
READ refuses an admin's write.
assistant_policy stays NONE after an admin sets it to READ_WRITE.
Confirmed write: proposing changes nothing. Confirming creates the record once (a second confirm gives 409), and the log row has POST /table/{tableName}, a 2xx status and the audit link.
Only the owner decides: another user gets "not found", and a rejected action can't be confirmed.
Only spec'd operations, never without a session.
Policy: the allowed-tools list and row cap are applied, and no hidden-field or password sentinels leak.
The golden and red-team evals now also run through the UI API.
Gates
./mvnw verify is green, including checkstyle, JaCoCo and ArchUnit, against QQQ 4.1.0-RC.4 and -Dqqq.snapshot.version=4.1.0-RC.5.
Upgrade
New tables assistant_data_access and assistant_pending_action.
New columns on assistant_tool_call and assistant_policy (see UPGRADE.md and docs/schema-postgres.sql).
The kof22 adoption is a separate draft Website-Backend PR.
Owner (2026-10-13): *"the agent should have to go through the same API that the UIUX does. that way it is held to the same security boundaries etc"*. The design and owner decisions are in `docs/DESIGN.md` ("Owner decisions (2026-10-13)", "Tools through the UI API").
## What changes
- **Every data tool runs a spec'd `/qqq/v1` operation with the user's own session.** `UiApiGateway` sends a loopback HTTP request carrying the `sessionUUID` cookie, so each call goes through the UI's authentication, permissions, record locks, validation, audit and error mapping.
- Only `MiddlewareVersionV1` spec'd operations can be called. Raw routes, including the assistant's own, are refused before any request, as is a call without a session.
- `LoopbackUiApiGateway` is a **stand-in for core's `UiApiInvoker` (QRun-IO/qqq#1128)** with the same contract. It will be replaced by an adapter once a QQQ release has it.
- `navigate` and `search_help` read no records, so they stay on the kernel.
- **Tools and their operations:**
- `list_tables`: `GET /metaData`
- `describe_table`: `GET /metaData/table/{t}`
- `query_records` and `count_records`: `POST /table/{t}/query` and `/count`
- `get_record`: `GET /table/{t}/{pk}`
- `search_records`: `POST /search`
- `render_widget`: `POST /widget/{w}`
- `get_record_history`: GET the record, then query `audit_table`, `audit` and `audit_detail`
- new `create_record`, `update_record` and `delete_record`: `POST`, `PATCH` and `DELETE` on `/table/...`
- **Data access** (`assistant_data_access`, a nav table): NONE, READ or READ_WRITE per table or process.
- Sensitive tables (AI exposure NONE) are NONE and cannot be raised. Other tables default to READ, processes to NONE.
- Data access only ever narrows what the user may do.
- **Writes need confirmation** (policy default).
- A write tool only creates an owner-locked `assistant_pending_action`, shown as a card (render `pending_action`).
- `POST /qqq/v1/assistant/actions/{id}/confirm` runs it once, through the UI API, with the confirming request's session. `.../reject` runs nothing.
- **Policy:** `writesRequireConfirmation`, `allowedTools`, `maxRowsPerCall` and `redactPii`.
- **Tool-call log:** adds the UI API operation, HTTP status, audit link and pending action. Inputs are stored with secrets redacted. Pending actions are a child list on conversations.
## Security tests (`UiApiToolsSecurityTest`, real QQQ application server)
Each case sends the UI's own request (a direct `/qqq/v1` call with the user's cookie) next to the assistant's tool call:
- **Read denied:** an analyst without `blog_post.read` gets **403 with the same message** from query, get and count, both ways.
- **Write denied:** an editor without insert or delete permission gets the UI's **403**, even with READ_WRITE and confirmation off.
- **Data access only narrows:**
- NONE hides a table the user can read, and no request is made.
- READ refuses an admin's write.
- `assistant_policy` stays NONE after an admin sets it to READ_WRITE.
- **Confirmed write:** proposing changes nothing. Confirming creates the record once (a second confirm gives 409), and the log row has `POST /table/{tableName}`, a 2xx status and the audit link.
- **Only the owner decides:** another user gets "not found", and a rejected action can't be confirmed.
- **Only spec'd operations, never without a session.**
- **Policy:** the allowed-tools list and row cap are applied, and no hidden-field or password sentinels leak.
The golden and red-team evals now also run through the UI API.
## Gates
`./mvnw verify` is green, including checkstyle, JaCoCo and ArchUnit, against QQQ 4.1.0-RC.4 and `-Dqqq.snapshot.version=4.1.0-RC.5`.
## Upgrade
- New tables `assistant_data_access` and `assistant_pending_action`.
- New columns on `assistant_tool_call` and `assistant_policy` (see `UPGRADE.md` and `docs/schema-postgres.sql`).
- The kof22 adoption is a separate draft Website-Backend PR.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Owner (2026-10-13): "the agent should have to go through the same API that the UIUX does. that way it is held to the same security boundaries etc". The design and owner decisions are in
docs/DESIGN.md("Owner decisions (2026-10-13)", "Tools through the UI API").What changes
/qqq/v1operation with the user's own session.UiApiGatewaysends a loopback HTTP request carrying thesessionUUIDcookie, so each call goes through the UI's authentication, permissions, record locks, validation, audit and error mapping.MiddlewareVersionV1spec'd operations can be called. Raw routes, including the assistant's own, are refused before any request, as is a call without a session.LoopbackUiApiGatewayis a stand-in for core'sUiApiInvoker(QRun-IO/qqq#1128) with the same contract. It will be replaced by an adapter once a QQQ release has it.navigateandsearch_helpread no records, so they stay on the kernel.list_tables:GET /metaDatadescribe_table:GET /metaData/table/{t}query_recordsandcount_records:POST /table/{t}/queryand/countget_record:GET /table/{t}/{pk}search_records:POST /searchrender_widget:POST /widget/{w}get_record_history: GET the record, then queryaudit_table,auditandaudit_detailcreate_record,update_recordanddelete_record:POST,PATCHandDELETEon/table/...assistant_data_access, a nav table): NONE, READ or READ_WRITE per table or process.assistant_pending_action, shown as a card (renderpending_action).POST /qqq/v1/assistant/actions/{id}/confirmruns it once, through the UI API, with the confirming request's session..../rejectruns nothing.writesRequireConfirmation,allowedTools,maxRowsPerCallandredactPii.Security tests (
UiApiToolsSecurityTest, real QQQ application server)Each case sends the UI's own request (a direct
/qqq/v1call with the user's cookie) next to the assistant's tool call:blog_post.readgets 403 with the same message from query, get and count, both ways.assistant_policystays NONE after an admin sets it to READ_WRITE.POST /table/{tableName}, a 2xx status and the audit link.The golden and red-team evals now also run through the UI API.
Gates
./mvnw verifyis green, including checkstyle, JaCoCo and ArchUnit, against QQQ 4.1.0-RC.4 and-Dqqq.snapshot.version=4.1.0-RC.5.Upgrade
assistant_data_accessandassistant_pending_action.assistant_tool_callandassistant_policy(seeUPGRADE.mdanddocs/schema-postgres.sql).🤖 Generated with Claude Code