On a host that enforces table permissions inside core actions (QInstance.enforcePermissionsInCoreActions), only an assistant administrator could use the assistant. A person with assistant.hasAccess and without assistant.admin was refused before any model call: "Not authorized to access the assistant admin data." (seen at BudgetService.evaluate -> AssistantStore.budgets). The assistant reads its settings and writes the person's own rows through tables whose permission rule is assistant.admin, with the person's session. A host without core enforcement never checked that rule for the store, so the qbit's own tests did not see it.
What ran with the person's table permissions, and what it is now
AssistantStore is the only package allowed to call QQQ data actions (ArchUnit). Every call it makes is now marked as the assistant's own service code (withPermissionBypass with a reason), the way qbit-collaboration and qbit-backups do. The session stays the person's in every case.
What the store touches
Kind
Before, where core enforces
Now
Policy, providers, models, model prices, profiles and skills, data access settings, knowledge sources
The assistant's configuration
Refused without assistant.admin
Read as the assistant's own code. Never returned to the person
Budgets, overrides, budget events, the shared budget accounting
Configuration and accounting
Refused
Read to decide; events and accounting written, attributed to the asking person
Conversations
The person's own data (owner lock, access rule)
Allowed
Unchanged: the person's session and the owner lock
Messages, turns, turn events, tool calls, pending actions, feedback, client errors, model calls (usage), daily usage, the data audit
The person's own data (owner lock) under an assistant.admin table rule
Refused
Written and read with the person's session; the owner lock keeps other people's rows out
What does not change:
Owner record locks. Core does not relax record locks for a bypass; a person asking the store for another person's conversation, messages or model calls gets no row.
The tables' permission rules. The tables stay closed to that person in the UI, the UI API and the assistant's tools (the AI exposure of every assistant table is NONE).
Who may call. Every operation of the assistant API requires assistant access first; the save rules of budgets and overrides require assistant.admin themselves.
No secret is returned: GET /config answers what it did, plus the person's own budgets.
A person's own budget
GET /config has budgets (UI API extension assistant 1.8.0, additive): the budgets scoped to that user or to a permission that user holds, with used, limit, percent, state, startsAgainAt and a sentence. Budgets shared by several people (INSTANCE, PROVIDER, MODEL) are not listed: their count is other people's spending too.
The same lines ride beside the person's question (<assistant_budget>, in the new message, not in the cached blocks), so "how much of my budget have I used?" has an answer.
Tests
CoreEnforcementTest (the fixture instance with core enforcement on), five tests:
A non-administrator's question with a tool call completes, and the model call is accounted to them.
The settings tables refuse that person through a core query, and the assistant's tools do not reach them.
One person cannot read another's conversation (API 404), messages or model calls (the store finds no row).
A person sees their own spending against their own limit in /config and beside their question; another person's figure is their own; the shared budget and the other person's amounts are absent; somebody without assistant access is told nothing.
A person at their own daily limit is refused with the sentence (HTTP 429); another person is answered.
Run without the store change, the first, second, third and fifth failed at the first store read. The fourth (own budget) was written afterwards.
./mvnw -B verify: BUILD SUCCESS, every module, no skip flags. One earlier run failed in TranscriptRedactionTest.expandedTokensSurviveMetadataValidation: its canaries are random UUIDs and it refuses any five-character window of them anywhere in storage, and that run's window was the digits 47278, which also occurred in a stored number. The test passed in three reruns and in the full run after. It is a chance collision in the test, not a leak; worth making the canary non-numeric.
## Why
On a host that enforces table permissions inside core actions (`QInstance.enforcePermissionsInCoreActions`), only an assistant administrator could use the assistant. A person with `assistant.hasAccess` and without `assistant.admin` was refused before any model call: "Not authorized to access the assistant admin data." (seen at `BudgetService.evaluate` -> `AssistantStore.budgets`). The assistant reads its settings and writes the person's own rows through tables whose permission rule is `assistant.admin`, with the person's session. A host without core enforcement never checked that rule for the store, so the qbit's own tests did not see it.
## What ran with the person's table permissions, and what it is now
`AssistantStore` is the only package allowed to call QQQ data actions (ArchUnit). Every call it makes is now marked as the assistant's own service code (`withPermissionBypass` with a reason), the way qbit-collaboration and qbit-backups do. The session stays the person's in every case.
| What the store touches | Kind | Before, where core enforces | Now |
|---|---|---|---|
| Policy, providers, models, model prices, profiles and skills, data access settings, knowledge sources | The assistant's configuration | Refused without `assistant.admin` | Read as the assistant's own code. Never returned to the person |
| Budgets, overrides, budget events, the shared budget accounting | Configuration and accounting | Refused | Read to decide; events and accounting written, attributed to the asking person |
| Conversations | The person's own data (owner lock, access rule) | Allowed | Unchanged: the person's session and the owner lock |
| Messages, turns, turn events, tool calls, pending actions, feedback, client errors, model calls (usage), daily usage, the data audit | The person's own data (owner lock) under an `assistant.admin` table rule | Refused | Written and read with the person's session; the owner lock keeps other people's rows out |
What does not change:
- Owner record locks. Core does not relax record locks for a bypass; a person asking the store for another person's conversation, messages or model calls gets no row.
- The tables' permission rules. The tables stay closed to that person in the UI, the UI API and the assistant's tools (the AI exposure of every assistant table is NONE).
- Who may call. Every operation of the assistant API requires assistant access first; the save rules of budgets and overrides require `assistant.admin` themselves.
- No secret is returned: `GET /config` answers what it did, plus the person's own budgets.
## A person's own budget
- `GET /config` has `budgets` (UI API extension assistant 1.8.0, additive): the budgets scoped to that user or to a permission that user holds, with `used`, `limit`, `percent`, `state`, `startsAgainAt` and a sentence. Budgets shared by several people (INSTANCE, PROVIDER, MODEL) are not listed: their count is other people's spending too.
- The same lines ride beside the person's question (`<assistant_budget>`, in the new message, not in the cached blocks), so "how much of my budget have I used?" has an answer.
## Tests
`CoreEnforcementTest` (the fixture instance with core enforcement on), five tests:
- A non-administrator's question with a tool call completes, and the model call is accounted to them.
- The settings tables refuse that person through a core query, and the assistant's tools do not reach them.
- One person cannot read another's conversation (API 404), messages or model calls (the store finds no row).
- A person sees their own spending against their own limit in `/config` and beside their question; another person's figure is their own; the shared budget and the other person's amounts are absent; somebody without assistant access is told nothing.
- A person at their own daily limit is refused with the sentence (HTTP 429); another person is answered.
Run without the store change, the first, second, third and fifth failed at the first store read. The fourth (own budget) was written afterwards.
`./mvnw -B verify`: BUILD SUCCESS, every module, no skip flags. One earlier run failed in `TranscriptRedactionTest.expandedTokensSurviveMetadataValidation`: its canaries are random UUIDs and it refuses any five-character window of them anywhere in storage, and that run's window was the digits `47278`, which also occurred in a stored number. The test passed in three reruns and in the full run after. It is a chance collision in the test, not a leak; worth making the canary non-numeric.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
On a host with QInstance.enforcePermissionsInCoreActions on, a person with
assistant.hasAccess and without assistant.admin was refused before any model
call ("Not authorized to access the assistant admin data."). The assistant
reads its settings (policy, providers, models, prices, budgets, data access)
and writes that person's messages, turns, tool calls, pending actions and
usage through tables whose permission rule is assistant.admin, with the
person's session. On a host without core enforcement nothing checked that
rule for the store, so it was never seen.
AssistantStore, the only package allowed to call QQQ data actions, now marks
every one of its calls as the assistant's own service code
(withPermissionBypass with a reason), the way qbit-collaboration and
qbit-backups do. Only the table permission check is skipped, for those calls:
- the session stays the person's: writes are attributed to them, and the
owner record locks keep every other person's rows out (core does not relax
record locks for a bypass);
- the tables' permission rules are unchanged: the tables stay closed to that
person in the UI, the UI API and the assistant's tools;
- who may call is decided before the store: every operation of the assistant
API requires assistant access, and the save rules of budgets and overrides
require assistant.admin themselves.
A person's own budget. GET /config has "budgets" (UI API extension assistant
1.8.0): the budgets scoped to that user or to a permission that user holds,
with used, limit, percent, state and when the count starts again. Budgets
shared by several people are not listed. The same lines ride beside the
person's question (<assistant_budget>), so "how much of my budget have I
used?" has an answer.
CoreEnforcementTest runs on the fixture instance with core enforcement on.
Run without the store change, four of its five tests failed at the first
store read; the fifth (own budget) was written afterwards.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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.
Why
On a host that enforces table permissions inside core actions (
QInstance.enforcePermissionsInCoreActions), only an assistant administrator could use the assistant. A person withassistant.hasAccessand withoutassistant.adminwas refused before any model call: "Not authorized to access the assistant admin data." (seen atBudgetService.evaluate->AssistantStore.budgets). The assistant reads its settings and writes the person's own rows through tables whose permission rule isassistant.admin, with the person's session. A host without core enforcement never checked that rule for the store, so the qbit's own tests did not see it.What ran with the person's table permissions, and what it is now
AssistantStoreis the only package allowed to call QQQ data actions (ArchUnit). Every call it makes is now marked as the assistant's own service code (withPermissionBypasswith a reason), the way qbit-collaboration and qbit-backups do. The session stays the person's in every case.assistant.adminassistant.admintable ruleWhat does not change:
assistant.adminthemselves.GET /configanswers what it did, plus the person's own budgets.A person's own budget
GET /confighasbudgets(UI API extension assistant 1.8.0, additive): the budgets scoped to that user or to a permission that user holds, withused,limit,percent,state,startsAgainAtand a sentence. Budgets shared by several people (INSTANCE, PROVIDER, MODEL) are not listed: their count is other people's spending too.<assistant_budget>, in the new message, not in the cached blocks), so "how much of my budget have I used?" has an answer.Tests
CoreEnforcementTest(the fixture instance with core enforcement on), five tests:/configand beside their question; another person's figure is their own; the shared budget and the other person's amounts are absent; somebody without assistant access is told nothing.Run without the store change, the first, second, third and fifth failed at the first store read. The fourth (own budget) was written afterwards.
./mvnw -B verify: BUILD SUCCESS, every module, no skip flags. One earlier run failed inTranscriptRedactionTest.expandedTokensSurviveMetadataValidation: its canaries are random UUIDs and it refuses any five-character window of them anywhere in storage, and that run's window was the digits47278, which also occurred in a stored number. The test passed in three reruns and in the full run after. It is a chance collision in the test, not a leak; worth making the canary non-numeric.🤖 Generated with Claude Code
On a host with QInstance.enforcePermissionsInCoreActions on, a person with assistant.hasAccess and without assistant.admin was refused before any model call ("Not authorized to access the assistant admin data."). The assistant reads its settings (policy, providers, models, prices, budgets, data access) and writes that person's messages, turns, tool calls, pending actions and usage through tables whose permission rule is assistant.admin, with the person's session. On a host without core enforcement nothing checked that rule for the store, so it was never seen. AssistantStore, the only package allowed to call QQQ data actions, now marks every one of its calls as the assistant's own service code (withPermissionBypass with a reason), the way qbit-collaboration and qbit-backups do. Only the table permission check is skipped, for those calls: - the session stays the person's: writes are attributed to them, and the owner record locks keep every other person's rows out (core does not relax record locks for a bypass); - the tables' permission rules are unchanged: the tables stay closed to that person in the UI, the UI API and the assistant's tools; - who may call is decided before the store: every operation of the assistant API requires assistant access, and the save rules of budgets and overrides require assistant.admin themselves. A person's own budget. GET /config has "budgets" (UI API extension assistant 1.8.0): the budgets scoped to that user or to a permission that user holds, with used, limit, percent, state and when the count starts again. Budgets shared by several people are not listed. The same lines ride beside the person's question (<assistant_budget>), so "how much of my budget have I used?" has an answer. CoreEnforcementTest runs on the fixture instance with core enforcement on. Run without the store change, four of its five tests failed at the first store read; the fifth (own budget) was written afterwards. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>