After an independent review of the process tools. Each change is described
by the guarantee it gives.
- Only a reviewed process runs, decided when the application runs. A host
registers the processes it reviewed for the assistant in
AssistantQBitConfig (withReviewedProcesses, or
withReviewedProcessesResource for a file on its classpath). With no list,
only a table's own bulk edit and bulk delete are runnable: they act on
records of a table the person may already change through the assistant.
With a list: exactly the listed processes and those two, each still
subject to every never-runnable rule, to an administrator's narrowing row
and to the person's own permission. RunnableProcessReview stays as the
way a host notices drift in its build, and now also fails when the
reviewed file is not the registered list.
- An input is only a field a screen asks a person for. A screen's field
whose name the process keeps for its own working is never an input
(ProcessControlNames: the inputs of the process's backend steps, and a
fixed set this QBit owns, checked by a test against the QQQ version it
builds with). The person is told the field cannot be set through the
assistant; the process stays runnable for its other fields.
- A person confirms what they were shown. A run stops at every later screen
that has something to show (a form, values to read, a text, a list of
records), and going on is a second card that shows it. Only a screen with
nothing to show is passed. The screen a run starts at is on the first
card.
- A run is over exactly the records its card counted. A filter is resolved
when the run is proposed, to at most 500 primary keys (more is refused,
for the person to narrow); the confirmed run is sent those keys and does
not start when one of them is gone.
- A process that may run as a durable job is created waiting, or not run:
where the application does not offer the operation that creates a job,
the run is refused.
- Text of the application reaches a model only inside the data envelope
every tool result uses: a process's messages, what a screen shows, the
application's message for a refusal or a failure. That holds for a
result, for a card's lines, for the summary a later request keeps of a
process step, for what a later request is told became of a confirmed run,
and for the thread's own message about it (ToolLabels.forModel,
MessageParts.modelText).
- A card goes on only with the run as it stood when the card was made: a
run counts a version up at every step and stop, and an older card of the
same run is refused.
One expectation of ChatToolSetTest changes: the scenario
everythingAsAnotherPerson no longer holds the four process tools. That host
registered no reviewed list, and its person may run processes of another
QBit and change no table, so the assistant runs no process for them.
ToolCallChecksTest is unchanged. No new tool. AssistantStore is still the
only code that calls data actions.
README, UPGRADE.md, CHANGELOG.md, CLAUDE.md and section 4.5 of
docs/actions-and-mcp.md say the same.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Process tools (package A4 of docs/actions-and-mcp.md), in tools/process:
list_processes, describe_process, run_process and get_run_status.
- The list and the description come from the person's own metadata, asked
as them: a process they may not run is never listed or described, and is
refused by name in the words used for a process that does not exist.
- run_process only proposes. Inputs are checked against the fields the
process's screens declare before a card exists (unknown input, wrong
type, missing required; no value is made up). The card (pending action
kind process_run) shows the process, every input, the records and the
steps that stop for the person.
- The confirmed run is the person's: it runs through the UI API with the
confirming request's session, and a run whose records are no longer the
ones the card counted does not start.
- A process eligible to run as a durable job is created as one and never
started by the assistant; any other runs through its own init and step
operations until it ends or needs the person (a review, a required field
with no value), where going on takes a new card.
- A confirmed run never holds its pending action RUNNING: the action ends
when the run was started, and get_run_status reports where it stands.
- What is never runnable through the assistant is decided in one place
(ProcessRunnability): denied to the assistant channel, the assistant's
own, data access not READ_WRITE, hidden, offered by no screen, on a table
the assistant may not change, or with a screen it cannot present.
Review findings, by the guarantee each gives:
- A host notices each process that becomes runnable through the assistant:
RunnableProcessReview fails the host's build when the runnable list
differs from its committed file.
- An administrator is warned before the data-access migration opens a
table they left read-only: UPGRADE.md has a query with the migration's
own predicate, tested against the changeset.
- No tool says confirmation can be turned off (the cached request prefix
changes once).
- No model is called without a spending limit in force: the routing
classifier asks for its own model. The administrator's Test connection
is the one documented exemption.
Also: a scheduled task is offered run_process only with its write
approval; a test that a confirmed update into a record-security key the
person lacks is refused by the application and changes nothing; UI API
extension assistant 1.10.0 (additive).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Package A2 of the actions design (docs/actions-and-mcp.md, sections 2 and 3),
with two owner decisions of 2026-11-12, and five findings of an independent
review of the tool boundary, each closed by the guarantee it gives.
Writes follow the person
- The data access of a table or process is declared in versioned code:
AssistantQBitConfig.withDefaultTableAccess / withDefaultProcessAccess (both
READ_WRITE) and a pin per object (withTableAccess, withProcessAccess).
READ_WRITE grants nothing: the assistant may propose what the signed-in
person may do themselves, and every change waits for their confirmation.
- An administrator's assistant_data_access row can only narrow (READ, NONE).
It never raises a code default, a pin or a table kept from AI.
- Every assistant_* table and every process of the QBit is NONE by a pin in
code that no row and no host pin lifts.
- The write tools are offered to a person who may create, edit or delete
records of some table (asked of the UI API as that person, once per offer,
within the tool-call time limit; no answer means no write tool). GET /config
features.writes is that answer for the person.
- Settings that cannot be read, and were never read, allow nothing.
- The table action "Add rows for every table" is retired. The changeset
assistant-data-access-follow-user removes the unedited READ rows it wrote,
and no other row.
No answer without a spending limit
- A person for whom no spending limit is in force (BudgetService.limitInForce)
is refused: the turn before anything is stored, every model call, every tool
call, every embedding request, a scheduled turn. Restoring a limit restores
service on the next request.
Review findings
- A pending action is never marked expired over its outcome: expiry is a
decision made through the same claim as confirming and cancelling.
- No action stays RUNNING for ever: retention ends one left running for more
than an hour as FAILED ("outcome unknown, check the record"), with a log row.
- A person is the session the authentication module resolved, not the object
passed in: a hand-made permission gives nothing, a revoked one is gone.
- The assistant's own reads get their context from the tool-call service, for
a person it checked; there is no public way to make a tool context from a
session id, a gateway and a policy.
- The published test-jar holds the testing package only.
- AssistantStore's decision methods are final.
See CHANGELOG.md and UPGRADE.md (what a host must apply before deploying).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Closes the findings of a third independent review of the tool boundary and
builds the pending-action package of the actions design on top.
Guarantees:
- A pending action is confirmed or cancelled once, by exactly one request,
on one server or several: the decision is an insert under a unique key
(new table assistant_pending_action_claim). Whatever fails after the claim
ends in FAILED or EXECUTED with a tool-call row, never in RUNNING.
- A tool cannot be run around the tool-call service from code outside the
assistant's packages: execute, log and withdraw are package-private; a
tool call request, a confirmed tool call and a tool context have no public
constructor and are made only with the service's permit; the runtime has
no public getter for the tool registry, the UI API gateway or its tool
families. A host's own ToolFamily and AssistantTool compile unchanged.
- A signed-in person is one person: a session with no HTTP request is made a
person only when the authentication module resolves the session's own id
to the user the session names. The turn engine no longer makes a person.
- Only the credential a request's session was resolved from is forwarded to
the UI API.
- A tool call that was cut short leaves no proposal anybody could confirm.
- The policy a tool call runs under is read fresh on every call.
- The hard budget is read on every call with one aggregate query per budget.
- The input kept in the tool-call log is redacted with the administrator's
custom patterns too.
Pending actions (design package A3):
- update_record and delete_record read the record as the person when they
propose; the card shows each field before and after (a delete: what the
record holds), after the exposure rules of get_record.
- What was approved is what is written: the proposal keeps a version of the
record (a digest, never values); a confirmed update or delete reads the
record again and refuses when it changed, is gone or cannot be read.
- actionKind (record_create, record_update, record_delete, or the proposing
tool's name), GET /assistant/actions (the person's own list), and
approvalRetentionDays is now read by the retention process.
- UI API extension assistant 1.9.0 (additive).
Schema: three columns and two indexes on assistant_pending_action, and the
table assistant_pending_action_claim (docs/schema-postgres.sql, UPGRADE.md).
ChatToolSetTest and ToolCallChecksTest pass unchanged.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
An independent security review of engine.ToolCallService (after PR 106)
found what a caller could still hand it. The chat's turn engine is the
only caller today and builds everything correctly, so none of it was
reachable; it is closed before an MCP endpoint calls the service. The
rule: every tool call runs as the signed-in person with their current
permissions and budget, never as the assistant; a refusal happens before
any data action; every call that ran has a log row with redacted input.
Numbered as in the review:
1. A caller cannot supply a policy. The service reads it
(PolicyService.current(false).tools(), narrowed for a scheduled
task's conversation) and builds the tool Context itself; a caller
hands it a tools.registry.ToolCall (person, id, name, input).
Every change always shows a confirmation card (owner, 2026-11-12):
ToolPolicy.writesRequireConfirmation is removed and UiApiToolbox
always proposes. The assistant_policy column stays and is no longer
read; no migration.
2. New SignedInPerson binds a session to the credentials forwarded to
the UI API. No public constructor: ofRequest(Context), only for
AssistantSpecs (ArchUnit), or ofSession(session). The context's
gateway is always runtime.getUiApiGateway(). AssistantApi and
TurnEngine lose withCredentials.
3. The hard budget is read on every call, not once per scope.
executeAndLog(call, conversationId, hipaaRoute) makes the scope.
4. confirm(person, pendingActionId) replaces executeConfirmed,
confirmationRefusal and logConfirmed: the service loads the stored
action of that person, checks it, claims it once, runs it, marks it
and writes the row. AssistantApi.confirmAction is a thin caller and
the only one (ArchUnit).
5. The toolboxes cannot be called around the service: their entry
points are package-private, the runtime has no getter for a toolbox
(each family is handed its own), and ArchUnit forbids calls into
tools.. from outside it except through the registry. The assistant's
own reads (starter prompts, knowledge re-read) use the new read-only
UiApiToolbox.read.
6. TurnEngine.executeTools writes each started call's row in a per-call
finally: after an interruption of the engine, after a runtime
exception while a result is processed or logged, and for a call cut
short by its time limit, with the real outcome when the worker
finished. A row that cannot be written for a call that changed data
is logged at ERROR.
7. Whatever fails after a confirmation's claim ends in FAILED with a
row, never in RUNNING.
8. The service checks that the scope's conversation and turn belong to
the call's person and works out the row's client channel itself
(ToolCallScope.clientChannel is gone). hipaaRoute stays the
caller's; the javadoc says why.
9. AssistantQBitConfig.withToolFamily throws once the QBit is produced.
Tests: ToolCallServiceTest (one per item, on the service directly),
ToolCallRowsTest (item 6; all three fail on develop), and
ToolCallArchitectureTest (items 2, 4, 5). ChatToolSetTest and
ToolCallChecksTest pass with unchanged expectations.
UiApiToolsSecurityTest's two tests that ran writes with confirmation off
now propose and confirm.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
engine.ToolCallService trusted its caller for several checks. The chat's
turn engine made them around the service; a caller with no chat turn (an
MCP endpoint, later) would have skipped them. The rule that now holds for
any caller: a tool call runs as the signed-in person with their current
permissions and budget, a refusal happens before any data action, and the
call is logged with redacted input.
1. execute refuses a tool the person is not offered now. It builds the
offer from the request's policy and session (once per scope). Before,
only the turn engine's allowed-tools set did, and navigate and
search_help ran although the policy had switched them off.
2. A request without a policy is refused. UiApiToolbox, KnowledgeTool
and BulkLoadTools read a missing policy as "every tool"; they refuse.
3. A request whose context was made for another session than its own is
refused, for a call and for a confirmation. ConfirmedToolCall carries
the confirming session.
4. New executeAndLog: runs the call and writes the assistant_tool_call
row, redacting the input itself, also for a refused call. Redaction
and the row moved out of TurnEngine (redactInput is gone; what a
presentation logs is AssistantTool.inputForLog). The turn engine runs
its calls in parallel under its own time limit and writes rows in
call order, so it uses the two halves, execute and
log(request, outcome); an ArchUnit rule keeps every other class on
executeAndLog. The chat's rows are unchanged.
5. The service's javadoc says what it enforces and what a caller still
supplies (rate limiting, per-request call limits and timeouts,
classification).
6. The confirm route refuses while the kill switch is on (503
KILL_SWITCH) and when the policy no longer allows the tool (403),
before the pending action is claimed, so it stays pending.
7. Tool families are registered explicitly: the QBit's own list
(AssistantRuntime.ownToolFamilies) plus
AssistantQBitConfig.withToolFamily for a host or another QBit. The
classpath scan is gone. The registered names are logged once at
startup; a duplicate name or an empty registry stops the start.
8. ToolRegistry.definitions sorts the keys of every object of an input
schema, so two JVMs send the same bytes (a Map.of schema iterated in
a per-JVM order, and the provider mappings serialise in iteration
order).
9. The service itself requires assistant access, checks the kill switch
(fresh) and the hard budget (the policy's hard limits and a stopped
budget row, read without a budget event or alert, when a scope's
first call arrives). New BudgetService.stoppedReason.
Tests: ToolCallServiceTest (fixes 1 to 4, 6, 9, called with no turn),
ToolRegistryTest (7, 8), ToolCallArchitectureTest (the new rule).
ChatToolSetTest and ToolCallChecksTest pass unchanged. Test helpers that
handed a tool a null policy now hand it the defaults.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A budget (Assistant > Budgets) can count over a WEEK: the ISO week in UTC,
Monday 00:00:00 UTC up to the next Monday 00:00:00 UTC. DAY and MONTH are
unchanged and there is no schema change (period is text).
The refusal at a limit now says which limit, how much, when the count starts
again and who can change it: "The assistant has reached the weekly limit of
$500.00 of the budget 'Everyone, a week'. It starts again on Monday 19 October
2026 at 00:00 UTC. An administrator can raise the limit or grant an override."
The warning says "weekly" for a weekly budget.
New config budgetCostLimitCeilingUsd (default 1,000,000, null for none): a
cost budget above it is refused when saved. A budget is created or changed
only by a session that holds assistant.admin; the save rule now checks it as
it already did for overrides, besides the table's permission rules.
Budgets are read from their table at every evaluation and are not cached, so
a changed limit applies to the next question. That was already so; it is now
stated and tested.
Tests (BudgetServiceTest): the week boundary in UTC, including a Sunday
evening in Chicago that is Monday in UTC and a week across a year end; the
micro-USD to dollar conversion at the limit (499,999,999 micros warn, one more
refuses); the refusal sentence and HTTP 429 BUDGET for a new question; a
changed limit taking effect on the running service; the ceiling; and a person
without assistant.admin refused by the table's permission and by the save.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The probe reports DISABLED, NOT_CONFIGURED, NO_PERMISSION, KILL_SWITCH in that order; GET /config gains an additive message naming what to configure; a user without access on an unconfigured server gets 503 NOT_CONFIGURED instead of 403. Extension 1.7.0.
Fixes#82
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Decode images within frame and pixel limits before provider use, contain malformed attachments per file, and preserve the host PDFBox font mapper.
Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
The turn engine reads pageContext.attachments through AttachmentService and
inlines the framed content (and images for vision models) into the
question's user message, after both cached system blocks. Unreadable files
become notices; image parts are dropped before a call to a model without
vision. In HIPAA mode attachments count as PHI. The system prompt treats
uploaded_file blocks as data and tells the model to propose writes with
create_record/update_record instead of saying it cannot create records.
GET /config reports features.attachments and the attachment limits.
Adds end-to-end, PHI routing and red-team tests.
Part of KofTwentyTwo/Website-Backend#305
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host-neutral help (deployment-specific facts removed), registered with
HelpBundles, gated by HelpBundleAssertions. QQQ 4.1.0-RC.7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Turn start keeps develop's supersede/concurrency, run tracker,
register and forceFinalize, and attaches the TranscriptRedactor.
- The shared finalization flushes redaction after the final events and
before the terminal turn update on every path (supersede, timeout,
restart recovery, forceFinalize); the redactor always closes.
- Token-bearing columns widened to TEXT (H2-safe upgrade statements),
entity length limits removed so tokens never truncate.
- Event reader race that could omit terminal events fixed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner requirement 2026-10-14: every assistant table should be populated and
visibly used, and an empty table should say how to set it up.
- db/liquibase/qbit-assistant/assistant-defaults.changelog.sql (host-included,
insert-only, self-guarding): default policy, default profile, four starter
skills linked to it, a $50/month instance budget (warn 80, downgrade 100,
stop 120), current Anthropic prices, a disabled example scheduled task.
- Assistant home Setup card: each config table, its state and the next step.
- assistant_data_access action "Add rows for every table" writes the default
access out as editable rows.
- help/qbit-assistant.yaml (help-bundle format) on the app and every table,
saying what it is for and how rows get created (STAND-IN(core=#1198)).
- Tests: guardrail (every table used at runtime, seeded or explained, with
empty-state help); changelog seeds a fresh H2 schema insert-only; seeded
rows drive persona, skills in the model request, budget warning, policy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Stored copies of tool results replace PII/PHI field values with
ClassifiedValueTokens.forRecordField tokens (core, QRun-IO/qqq#1182);
model-written text, persisted text deltas, presentation parts, tool
inputs, run summaries and audit reasons have every classified value
the turn saw replaced by its token. What the model receives (as the
PHI routing allows) and the live SSE to the entitled user are
unchanged.
- Pending writes that would carry a classified value are refused rather
than stored (follow-up: an encrypted short-lived payload).
- A core log redaction hook masks seen classified values in assistant
logs.
- Canary test: CANARY-PHI/PII markers never appear (nor any >4-char
substring) in any assistant_* table, logs or audits, while the model
did receive them in memory.
- Requires QQQ 4.1.0-RC.7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The qbit-assistant-provider-api artifact now holds exactly one package,
com.kingsrook.qbits.assistant.spi (23 types including ModelPricing), so
consumers such as qbit-marketing depend only on that artifact and the
qbit-bom G1 guardrails can allow it by package. BREAKING: imports change
from ...assistant.provider.X / ...assistant.engine.ModelPricing to
...assistant.spi.X (UPGRADE.md has the table). ArchUnit forbids split
packages between the API and the implementation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves the optional email integration from qbit-email internals to
com.kingsrook.qbits.email.api (qbit-email 0.1.0-RC.2) with identical
behaviour, and moves to qbit-build-parent 2.1.1, whose default G1
allow-lists include qbit-email's API (QRun-IO/qbit-bom#12), so the
per-repo allow-lists are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Default off (owner: hard wall; kof22 does not opt in). When on, calls
core's ClientChannelsMetaData.withAssistantAuditReads() (QRun-IO/qqq#1193:
audits readable, never writable, worm_audit_* still denied) through a
STAND-IN(core=QRun-IO/qqq#1193, removeBy=RC.8) lookup, falling back on
RC.6 to removing exactly the audit* pattern. Other patterns are never
touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps develop's changes and re-applies the optional qbit-email 0.1.0-RC.1
delivery on top (adapted to develop's refactors; guardrail allow-lists
for the optional integration where the build parent enforces them).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X1:
- Every assistant_* table, process, report and widget (and every object
the qbit produces) is denied to client channel assistant: the
assistant_* pattern in ClientChannelsMetaData plus
QPermissionRules.withDeniedClients on each object.
- Startup fails if any of them is not denied.
- Every UI API call carries assistant/<turnId> (scheduled:
assistant/scheduled/<taskId>); confirm and explain-access carry the
owning turn.
- Tests: assistant.admin through the assistant is denied (403, absent
from list_tables and /metaData) on every configuration object, while the
same admin through next-ui is allowed; the startup check fails on one
undenied object; core's security-surface patterns are present.
Y1:
- assistant_tool_call.audit_href: an /app link to the written record's
audit rows filtered by clientChannel, built with the shared filter-link
builder.
- Tests: an assistant write audits the human actor with
clientChannel assistant/<turnId>; the same write from the UI audits
next-ui. Hosts enable the audit clientChannel field (documented).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scheduled runs go through the invoker as assistant/scheduled/<taskId>;
the designated-user manageSession login stays a plain UI-client login.
get_record_history is offered only when the host opts the audit tables
in (core's default wall denies audit* to the assistant). Reserved-header
filtering in the stand-in header invoker is tested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CoreUiApiGateway on UiApiInvoker for every tool (P tools, bulk-load,
presentation), client channel assistant/<turnId>; form params mapped to
withFormParam; LoopbackUiApiGateway deleted.
- Extra headers (X-QQQ-AI-Route) through HeaderedUiApiInvoker, a stand-in
until core UiApiRequest has withHeader.
- Core DataClassification replaces the qbit stand-in; the default
classification source merges core metadata with the config map.
- Bulk-load confirm dispatch kept on the spec'd confirm path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase (d) of QRun-IO/qbit-email docs/DESIGN.md section 11. qbit-email is an
optional dependency (0.1.0-SNAPSHOT until its first RC) used through one
adapter class behind a classpath and instance guard; without it the
existing behaviour is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Architecture review QRun-IO/qqq#1150 F5: qbit-marketing compiled against
assistant internals.
- New artifact qbit-assistant-provider-api with the provider SPI and
value types (same package names) and engine.ModelPricing; production
dependency jackson-annotations only. An ArchUnit test keeps it free of
assistant internals.
- AssistantProviderType's settings validation takes ProviderSettings
instead of the assistant_provider entity (documented in UPGRADE.md).
- qbit-assistant and the provider modules depend on the API artifact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assistant_scheduled_task (Scheduled tasks): prompt, run-as user,
credential env-var reference (allow-listed, never stored), profile,
Quartz cron + time zone, output mode, write-mode request and per-task
admin approval (cleared when the prompt, user, credential, profile or
request changes), per-run cost cap; assistant_scheduled_run child list.
- Runner (60 s schedule): claims a slot via a unique (task, scheduledFor)
run key, logs in exactly like a UI client (POST /qqq/v1/manageSession
with accessToken), checks the session's user is the run-as user, then
runs a normal turn on a new conversation (channel assistant/scheduled,
X-QQQ-Client assistant/scheduled/<taskId>). No system or constructed
sessions.
- PHI routing, budgets, routing/fallback apply unchanged; output as the
conversation and run record, a notification through core alerts, or
both. Record actions: Approve write mode, Revoke write mode, Run now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner priority: answers must be easy to read and act on.
- present_results tool: summary, metrics, table (computed percent
columns, per-row filtered-list links), bar/pie/line chart from the
table, filter and record links, follow-up actions. Every table and
field is checked against what the user can see through the UI API;
hrefs (/app/<table>?filter=<QQQ filter JSON>, records) and number
formats are built server-side from metadata; the model never writes
URLs.
- presentation message part (persisted, SSE event) with a server-built
multi-line GFM markdown fallback for clients without blocks.
- Static system-prompt Presentation section (built-in default skill):
answer first, no restating, tables for 3+ items, charts for shares and
trends, filterLinks whenever a list is offered.
- Golden eval of the owner's example: summary, 2-row table with 80.2% /
19.8%, a 2-point pie and two mimeType filter links; negative cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Embeddings: EmbeddingProvider SPI, OpenAI-compatible /embeddings
(batching, Azure path), assistant_model.model_kind CHAT/EMBEDDING and
embedding_dimensions; chat selection ignores embedding models.
- VectorStore SPI: in-memory store, and the new qbit-assistant-rag-pgvector
module (one schema per instance, HNSW cosine index, parameterized SQL).
The index holds NO record content: table, key, field, chunk, hash and
vector only.
- assistant_rag_source (Knowledge sources) with Index now / Clear index
record actions that read through the UI API as the clicking admin,
skip unchanged chunks and drop removed records.
- search_knowledge tool: vector search, then every hit re-read through the
UI API with the asking user's session (403/404 hits dropped), AI field
exposure and masking applied, /app citations; PHI rule for embeddings;
classification raised by results.
- pgvector container test (pgvector/pgvector:pg16) passes with Docker.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes#28 (first half; the DataClassification and loopback gateway
stand-ins go with RC.6).
- Default QQQ is 4.1.0-RC.5.
- BudgetAlerts calls QAlerts.raise(AlertSignal...) directly instead of
binding by reflection.
- The producer declares assistant.budget.warning, .downgraded and .stopped
as QAlertTypeMetaData (assistant.admin, subject assistant_budget).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Profiles gain routing (OFF, HEURISTIC, CLASSIFIER) with simple, complex
and classifier models. The heuristic is a small documented rule set; the
classifier is one 4-token call on the question text only (ROUTE usage,
3 s timeout, heuristic on any failure, skipped for PHI on a
non-eligible provider).
- assistant_model.fallback_model_id chains (cycle-checked, max 4 hops)
across providers; retryable failures before any output fall back.
Candidates that are disabled, down, over a STOP budget or fail the PHI
rule are skipped; without a chain, LOCAL falls back only to LOCAL.
- Circuit breaker (provider health UP/DOWN, down_until) and a cached
health probe for stale LOCAL providers.
- A mid-turn provider switch rebuilds history from neutral parts and drops
adapter state (Anthropic thinking signatures).
- Turn route decision, source and fallback count; home shows health and
fallbacks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assistant_budget (scope INSTANCE/USER/ROLE/PROVIDER/MODEL, DAY/MONTH,
cost or tokens, warn %, DOWNGRADE or STOP at 100%, hard ceiling),
assistant_budget_override (reason, grantor, expiry) and
assistant_budget_event (deduplicated WARNED/DOWNGRADED/STOPPED).
- BudgetService checks before every model call; strictest outcome wins.
Warnings raise a panel notice and an optional Alerts signal; downgrade
prefers a cheaper LOCAL model and never crosses the PHI rule.
- assistant_budget_usage: an owner-lock-free accounting projection of
usage counters so shared-scope sums run under the caller's own session.
- App home: budgets card with state and overrides, 30-day cost and token
bars with user, model, provider and tool breakdowns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- DataClassification (PUBLIC < CONFIDENTIAL < PII < PHI): a qbit
stand-in for core's (QRun-IO/qqq#1142) behind a pluggable
ClassificationSource (config by default; core metadata or
qbit-secured-data overrides later).
- Each turn tracks the highest classification its page context, history
and tool results touched (turn.classification, conversation
max_classification, never lowered).
- PhiRoutingGuard before EVERY model call: PHI only to hipaa_eligible
provider rows; otherwise the turn ends PHI_BLOCKED with the reason and
the tool results are never sent.
- HIPAA mode: no non-eligible providers (resolve, enable, code fallback),
AES-256-GCM conversation storage (key by env-var reference, required),
PHI retention period, no provider messages or stack traces in logs.
- assistant_data_audit: one row per model call that carried PII/PHI and
per block (who, classification, tables, provider, model, decision).
- System rule 11: masked values stay masked; never ask to reveal them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- qbit-assistant-provider-bedrock: Claude on Bedrock through the
anthropic-java Bedrock backend (AnthropicMapping reused), credentials
from the server's AWS default chain, region required, no key reference.
IRSA/web identity needs the host to add software.amazon.awssdk:sts.
- qbit-assistant-provider-vertex: Claude on Vertex through the
anthropic-java Vertex backend, Gemini through Vertex's OpenAI-compatible
endpoint with an ADC bearer token refreshed per request (never strict).
Region and the new cloud_project column are required.
- AnthropicModelProvider.Builder.client(...) and a bearer-token supplier
on ProviderSettings / the OpenAI adapter.
- Mocked contract tests: a Bedrock event-stream mock with SigV4 checks,
Vertex rawPredict and OpenAI-compatible mocks, validation, and a golden
tool round trip per module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assistant_provider gains kind, location, hipaa_eligible, region, api
version, timeouts, a CA bundle reference and discovery status.
- New assistant_model table (child list on the provider): capabilities,
prices per million tokens, enabled flag, last test.
- Provider SPI: ProviderSettings, kinds, discovery and health as default
methods; create(apiKey, baseUrl) implementations keep working.
- New module qbit-assistant-provider-openai: OpenAI, Azure OpenAI,
OpenRouter, Ollama, LM Studio, vLLM and llama.cpp over JDK HTTP, with
streaming, tool calls, usage, timeouts, cancel, strict only where
supported (and the existing fallback on refusal), keyless local
endpoints and per-provider private CA trust.
- Record actions: Discover models (provider), Test connection (model);
provider Test connection checks health first.
- Mock-server contract tests, a golden tool round trip, TLS tests and a
config-driven live check (skipped unless configured).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- assistant_provider: type (registered AssistantProviderType; Anthropic),
model, enabled/default, base URL override, cost cap, output and
concurrency limits, last connection test. The key is never stored: the
row names an allow-listed environment variable; apiKeyStatus is
computed on read (last 4 at most) and dropped on save.
- Test connection: a record action (standard streamed ETL Review/Result)
making one short live call; result saved on the row and as a TEST
usage row.
- assistant_usage: one row per model call (turn steps and tests);
assistant_model_price: editable USD/MTok used for every call's cost.
- The Assistant app home (health, providers, usage by day, costs vs caps,
by user, top tools, recent failures); nav = home + main tables.
- AI exposure NONE on every assistant_* table.
- Tests, README, DESIGN, UPGRADE, CHANGELOG, reference DDL with the
Anthropic price seed, demo server and screenshots.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QQQ core's standard permission suffix is hasAccess
(PrivatePermissionSubType.HAS_ACCESS) and the assistant base already
follows the naming rule, so the permission stays assistant.hasAccess,
matching the sql-workbench rename. Drops LEGACY_ACCESS_PERMISSION and
updates README, CHANGELOG, UPGRADE.md and NamingTest. Table, PVS and
key-type renames are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AssistantInstanceMetaData exposes getFrontendSettings() ({version, basePath}),
which the v1 middleware (QRun-IO/qqq#1078) publishes as
supplementalInstanceMetaData.assistant next to available=true. The probe
test now runs end to end. README: allowedWidgetNames is the only widget
opt-in (core widget meta-data has no supplemental hook).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Kernel settings from config: allowedWidgetNames (kernel default-deny),
hiddenWidgetNames, customRedactionPatterns.
- Orphaned RUNNING turns of a dead replica are finished as ERROR on first
sight (replay, cancel, delete, new turn) with persisted error(INTERNAL) +
message.end; replay is paged (500) and capped at maxTurnSeconds + 60 s.
- Retention: tool-result cutoff also deletes turn events and strips render
payloads / record refs from stored message parts.
- addUsage: per-key locks, unique key (scope, userId, usageDate), insert-race
retry; partial usage of cancelled / timed-out / failed calls is counted.
- Kill switch fails closed; a per-turn watcher cancels running turns when it
flips.
- Page-context scalars capped (512 chars, 100 ids); list limits for
conversations and running turns.
- Terminal events persisted before the turn status becomes final.
- "assistant" supplemental instance meta-data for the Next probe gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ArchUnit rules: no system session, no QSession construction, data actions
only in the persistence package.
- Permission red team (build gate): no-permission denial without leaks,
other users' conversations, kill switch mid-turn, hard budget, hidden and
password sentinels never sent, enveloped tool results, no thinking in SSE,
cancel stops pending tools.
- Golden evals v1 (scripted faithful model) incl. example D and the read
half of example A, plus an opt-in live run against Claude.
- Record Developer screen Assistant panel on the core developer-panel
contract (QRun-IO/qqq#1076) fed by AiToolKernel.describeAccess, with the
ask-the-assistant deep link.
- README, CLAUDE.md, CHANGELOG, reference DDL, CircleCI (qqq-orb 0.6.8).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>