feat!: assistant tools go through the /qqq/v1 UI API with the user's session (phase b) #14

Merged
KofTwentyTwo merged 1 commits from feature/ui-api-tools into develop 2026-10-13 18:59:36 +00:00
KofTwentyTwo commented 2026-10-13 18:56:39 +00:00 (Migrated from github.com)

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

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)
Sign in to join this conversation.