Files
qbit-assistant/UPGRADE.md
james.maesandCodex 3637ca3ad0 fix: integrate conversation management with current develop
Preserve current attachment, notification and starter prompt behavior. Encrypt user titles that resemble ciphertext unless they match the same stored record; verify all eight reactor modules.

Co-Authored-By: Codex <noreply@openai.com>
Signed-off-by: James Maes <james@kof22.com>
2026-10-19 09:52:41 -05:00

36 KiB

Upgrading qbit-assistant

Conversation management (Unreleased, UI API extension assistant 1.6.0)

No schema change. assistant_conversation is now usable by every user holding assistant.hasAccess (not only assistant.admin), together with three new record actions (Archive, Restore and Delete conversations). Rows stay locked to their owner. A user's table queries only return their own rows if their session carries their owner key: call AssistantSecurity.applySessionKeys from the authentication customizer for every user, not only for admins. Without it, non-admin users see an empty table.

The panel can link to the table using conversationsTable from GET /config. That field is present only when the user may query the table.

Starter prompts follow the user's access (Unreleased)

GET /assistant/config no longer returns the profile's starter prompts as written. profile.starterPrompts (same field, still string[]) is now built per user:

  • Up to three prompts are generated from the tables the signed-in user can read through the assistant and that have rows (a count as the user), using the table labels, a date field when present, and "What changed in ... this week?" when the change-history tool is offered. The pick is stable for the user for the day and rechecked on every request, including table, row and field access. Generation uses bounded UI API calls and no model calls. Any failure yields no generated prompts.
  • A configured starter prompt (Assistant Profile > Starter prompts, one per line) is shown only when it declares the tables it needs and the user can read all of them. Declare them with a leading tag: [blog_post] Which posts are drafts?, or [blog_post, page] ... for several. [none] What can you help with? names no table and is always shown. A line without a tag is no longer shown, because nothing says which tables it mentions: add a tag to keep it.
  • Shown prompts are the permitted configured ones first, then generated ones, three in all (a profile with three or more permitted prompts shows only those).

No schema change and no configuration. The panel needs no change.

Chat turns are not jobs (Unreleased)

Ordinary chat turns no longer create core tracking jobs, so they no longer appear in the Jobs tray or the Jobs tables and no longer trigger a per-answer "finished" notification. No schema change and no configuration is needed.

The AssistantRunTracker SPI and JobTrackingRunTracker are removed, along with AssistantQBitConfig.withRunTracker, setRunTracker and getRunTracker. A host that called any of them must delete the call: withRunTracker(AssistantRunTracker.NOOP) is now the only behavior, and a custom tracker has no replacement hook. Turn progress, Stop and recovery are available through the assistant's own routes (GET /turns/active, the turn events stream and POST /turns/{id}/cancel).

Bulk loads the assistant launches are still durable jobs. Their confirmed tool result now includes jobHref (the job's in-app link) and the confirm response's href points at the job.

Scheduled task notifications now use core QAlerts.notify; they reach whatever alerts sink the host installed (or core's logging sink when none is).

Provider setup state (Unreleased)

Apply the Provider setup state block at the end of docs/schema-postgres.sql before deploying: the provider table now reads setup_state, so the new jar fails on assistant_provider without it. It adds a nullable VARCHAR(16) with default 'ACTIVE' and a check for ACTIVE / SETTING_UP; existing rows behave as before. It is IF NOT EXISTS and safe to re-run.

To stop an unfinished provider from being routed to or health-checked, edit it in Assistant > Assistant Provider (Provider section) and set Setup state to Setting up (SQL: UPDATE assistant_provider SET setup_state = 'SETTING_UP' WHERE id = <id>;). Enabled providers that are neither the default nor the only route for a configured model now report DEGRADED, not DOWN, when unreachable. No configuration change.

Fail soft (Unreleased, UI API extension assistant 1.5.0)

Apply the Fail soft block at the end of docs/schema-postgres.sql before deploying. It creates assistant_client_error (errors the panel reports through POST /qqq/v1/assistant/client-errors) with its indexes and SET NULL foreign keys to the conversation, message and turn. It is IF NOT EXISTS and safe to re-run.

New configuration, all optional: toolCallTimeoutSeconds (default 60), clientErrorsPerUserPerMinute (default 30) and clientErrorWarnPerHour (default 20, when the assistant.client-errors health check turns degraded). The new table is in the Assistant app's nav for administrators.

Transcript redaction (Unreleased)

Apply the transcript-redaction column widening block in docs/schema-postgres.sql to existing databases. Stop reasons, request IDs, tool-call IDs, feedback/data-audit/ budget-override reasons and scheduled-run summaries/errors now use TEXT; token expansion must not fail a write or truncate secured-data tokens. The corresponding entity field limits are removed, including pending-action and tool-result summaries.

Foreign keys (2026-10-14)

Recommended: apply the "Foreign keys" block at the end of docs/schema-postgres.sql (after the index audit). It is guarded and safe to re-run.

  • Adds the missing foreign keys across the conversation tables, usage, data audit, budget accounting and profile skills, with the indexes they need.
  • Detail rows of a conversation CASCADE; the message/turn cycle, usage and the data audit use SET NULL, so usage keeps its cost rows when a conversation is deleted.
  • Usage, budget accounting and the data audit hold their provider (NO ACTION): disable a provider with history instead of deleting it.
  • assistant_model.provider_id changes from NO ACTION to CASCADE (constraint renamed fk_assistant_model_provider).
  • Rows that point at a missing parent are first copied to qqq_fk_quarantine (created if absent), then nulled, or deleted for the NOT NULL profile-skill links. Check that table after the upgrade.

No code change.

Index audit (2026-10-14)

Recommended, not required: apply the "Index audit" block at the end of docs/schema-postgres.sql. It adds six indexes, every one IF NOT EXISTS:

  • usage by turn (read on each scheduled run)
  • the three retention date filters (turn events, tool calls, inactive conversations)
  • the two ON DELETE SET NULL foreign keys on assistant_scheduled_run

No code change.

Durable turns (Unreleased)

Apply the Durable turns block at the end of docs/schema-postgres.sql before starting the new runtime. It adds nullable heartbeat_at, lease_owner, cancel_requested_at, phase, concurrency_mode and the (user_id, status) index. A partial unique index on terminal events also prevents two replicas from inserting separate message.end frames for the same turn. Check for historical duplicate terminal frames before applying it (resolve any duplicates through your normal migration process). Fresh installs run the full schema, including ALTERs. Existing rows with no heartbeat use started_at for lease expiry. During the first upgrade from a runtime without heartbeats, drain old replicas' turns first: a new replica cannot distinguish an old live turn from a dead owner after 45 s.

Defaults: heartbeat 5 s, lease 45 s, hard duration 300 s, force-finalization grace 20 s, shutdown grace 8 s. Configure these through the corresponding AssistantQBitConfig withers. Heartbeat must be shorter than the lease. The JVM shutdown hook is enabled by default; tests disable registerShutdownHook and managed hosts may call runtime.shutdown(Duration) themselves. Give the process termination budget enough time for the configured shutdown grace and persistence.

POST /conversations/{id}/turns accepts concurrency: "supersede" | "concurrent" (case-insensitive). Default SUPERSEDE stops prior runs in that conversation; CONCURRENT permits them. The default per-user cap is now 3; reaching it stops the oldest run instead of returning the old in-progress 429. Per-minute and budget 429 responses still apply. Superseded turns record cancelReason=SUPERSEDED.

Cancel now returns HTTP 200, including for terminal turns, with the current status. The OpenAPI fragment retains the old 202 response for mixed-version rollouts. Active runs are discoverable through GET /qqq/v1/assistant/turns/active; reattach through the existing events route. Consumers should recognize turn.phase and message.start.startedAt. Starts during shutdown return 503 SHUTTING_DOWN; clients may retry against a ready replica.

JobTrackingRunTracker is now the default AssistantRunTracker, using core JobTracking in QQQ 4.1.0-RC.8 to publish answers in the Jobs tray. The host must provide the jobs tables (through qbit-jobs / core DurableJobsMetaDataProvider) and run DurableJobsRuntime. Without that runtime, answers complete normally and are simply not tracked.

Each job is owned by the assistant user, links to its assistant_conversation, and reports thinking, tool and writing phases. Tool progress contains only the tool label; terminal messages are fixed and never contain prompts or answers. The tray's Stop cancels the assistant turn. All tracker callbacks remain isolated from turn execution. Opt out with withRunTracker(AssistantRunTracker.NOOP); hosts may still supply their own AssistantRunTracker.

Starter defaults, setup card and table help

The jar now ships db/liquibase/qbit-assistant/assistant-defaults.changelog.sql. Include it from the host's master changelog after the changesets that create the assistant_* tables:

- include: { file: db/liquibase/qbit-assistant/assistant-defaults.changelog.sql }

It is insert-only and never changes a host that already configured the assistant: the policy, the $50/month instance budget (warn 80%, downgrade 100%, stop 120%) and the Anthropic list prices are seeded only into empty tables, and the default profile only when no default profile exists. Price rows take precedence over AssistantQBitConfig.withPricing, which is why they are seeded only when there are none. The four starter skills and a disabled example scheduled task are always added (by name); on a host with its own default profile the skills are not linked to it, and the Setup card says so. Requires Liquibase 4.x or later (preconditions are sql-checks).

No DDL changes. New: the Assistant home's Setup card, the assistant_data_access table action Add rows for every table (assistantDataAccessAddDefaults, admin only) and bundle help on every assistant table (help/qbit-assistant.yaml).

X1/Y1: channel wall and audit attribution

Requires QQQ 4.1.0-RC.6. Every assistant table, process, report and widget is explicitly denied to channel assistant; startup rejects an exposed object. Host customizations must preserve these denials. Administration uses next-ui.

Before enabling AuditsMetaDataProvider.withClientChannelField(true), add the nullable audit.client_channel VARCHAR(100) column using the host migration conventions. kof22 needs a feature changeset under changesets/features/ (for example 2026-10-14-audit-client-channel.sql); no host migration is applied here. Existing audit rows remain null. The test fixture enables the field.

The existing assistant_tool_call.auditHref column now points to audit rows filtered by record, table and client channel, including for deletes. It is empty when the host has not enabled the audit channel field. Apply the X1/Y1 DDL in docs/schema-postgres.sql to widen the existing assistant_tool_call.audit_href column to TEXT: encoded filters with long table names or string record ids can exceed the old 500-character limit. No new QBit column is required. GET /assistant/access accepts an optional owned turnId for attribution.

Provider API extraction (#30)

BREAKING: every provider API type now lives in the exclusive package com.kingsrook.qbits.assistant.spi. Update imports and recompile consumers. Hosts still receive qbit-assistant-provider-api transitively from qbit-assistant. No schema migration is needed.

Old import New import
com.kingsrook.qbits.assistant.provider.X (API types) com.kingsrook.qbits.assistant.spi.X
com.kingsrook.qbits.assistant.engine.ModelPricing com.kingsrook.qbits.assistant.spi.ModelPricing

Internal classes such as provider.RegistryModelProvider and provider.fake.ScriptedModelProvider stay in the runtime and are not API types. External consumers such as qbit-marketing depend only on com.kingsrook.qbits:qbit-assistant-provider-api for these contracts and allow package com.kingsrook.qbits.assistant.spi in their guardrails. qbit-bom will add this package to the G1 defaults.

External providers and applications using only provider/pricing types should replace their qbit-assistant dependency with com.kingsrook.qbits:qbit-assistant-provider-api at the same version. This removes the assistant runtime and QQQ from that dependency path; only Jackson annotations are required by the API. Anthropic, OpenAI, Bedrock and Vertex adapters use this split; pgvector retains qbit-assistant for its runtime RAG interfaces.

Recompile custom provider types that override validate(AssistantProvider) and change the signature to validate(ProviderSettings). The settings carry the configured kind, URL, API version, region, project, CA bundle name, timeouts and apiKeyEnvName; the key and bearer-token supplier are not resolved for validation. Providers using the default validator need only the import migration. Keep the runtime and provider artifacts on the same version; old validator overrides must be rebuilt.

P-g: scheduled tasks

Apply the P-g DDL in docs/schema-postgres.sql before deploying. The unique (task_id, scheduled_for) constraint is required for multi-replica safety; retain run rows as schedule-slot claim history. Existing conversations default to channel panel. Tasks start disabled.

Set withScheduledTasksSchedulerName(...) to an existing QQQ scheduler and configure withUiApiBaseUrl(...) with this replica's own listener URL. The runner polls every 60 seconds. QQQ must supply a scheduler session able to manage the assistant tables; this QBit never creates that session. The configured backend must enforce unique keys atomically (Postgres does).

Allow only designated credential references with withAllowedScheduledCredentialEnvNames(Set.of("ASSISTANT_SCHEDULED_TOKEN")). Provide that variable through the host's secret manager. It must hold a token accepted by the host's manageSession authentication provider for runAsUserId. The runner fails closed on missing/disallowed credentials or an identity mismatch. See scheduled-task setup for kof22 provisioning.

QQQ RC.5 has no conditional UpdateInput. The runner uses a database-unique schedule-slot insertion as its optimistic claim, then advances nextRunAt before starting work. Crashes can skip a slot; they never retry it. No ArchUnit exception or fabricated session is introduced. Core RC.6 enforces client-channel denials; scheduled requests carry assistant/scheduled/<taskId>.

Notifications use the optional Alerts QBit notification SPI with a single-user audience. Without it, delivery logs run id/status only. All output modes retain the owner-locked audit row and conversation. HIPAA run summaries never copy the answer; they contain only see conversation.

P-f: knowledge retrieval

Apply the P-f assistant-table DDL in docs/schema-postgres.sql before deployment. It adds assistant_model.model_kind (existing rows default to CHAT), embedding_dimensions, and the admin-only assistant_rag_source table. No sources are seeded. Existing chat selections remain unchanged; explicitly EMBEDDING models are excluded from chat, classifier, fallback and downgrade selection. Correct discovery's kind/dimensions if a model name is ambiguous.

For persistent vectors, add qbit-assistant-rag-pgvector at the same version as the QBit, supply the host's JDBC DataSource/driver, and install pgvector in the database. A DBA runs CREATE EXTENSION IF NOT EXISTS vector; the application role needs schema creation rights for initial setup (or a DBA-created schema), then normal DML rights. ensureSchema() checks for an existing extension first and does not require extension installation privileges when it is already present. The optional pgvector DDL is a commented reference block, separate from the assistant-table migration. See RAG setup.

Use a distinct validated schema for each QQQ instance. vector(N) fixes dimensions for that store; changing dimensions requires a new schema and re-indexing. Sources can use different models with that dimension; ranking is restricted to sources using the query's model. If the weights behind an unchanged model id change, clear and rebuild its sources. Editing source chunk/filter/model settings or registry model identity/dimensions automatically clears affected vectors.

Every indexing batch produces EMBED usage and updates existing budget rollups. PHI source content and PHI conversation queries require a HIPAA-eligible embedding provider. No background runner or designated credentials are installed in P-f. The default in-memory store is lost on restart and must be indexed again.

P-e: profile routing and provider fallback

Apply the additive P-e ALTER TABLE statements at the end of docs/schema-postgres.sql before deploying. Existing profiles default to OFF; optional model references and health columns start null. No routing or fallback configuration is seeded. Existing turns retain unknown routing/final-provider values; fallback_count defaults to zero.

Profiles now have a Model routing section. Select HEURISTIC or CLASSIFIER, then optional simple/complex registry models and a classifier model. Empty route slots retain provider/policy selection. CLASSIFIER with no usable classifier uses HEURISTIC. OFF preserves prior provider selection. With no effective profile, code-configured escalation supplies the complex model/effort when enabled.

Set a model's Fallback model to opt into a cross-provider chain. Saves reject cycles, more than four edges, or disabled/missing targets. Provider health is read-only; NETWORK/TIMEOUT opens a 60-second circuit, and successful calls close it. Local endpoints with stale health get a bounded two-second probe. Automatic fallback without an explicit link is confined to local models of the same adapter family. PHI/HIPAA eligibility and budgets apply to every destination.

Classifier usage is recorded with purpose ROUTE and included in budgets. Reports that filter usage purpose should include ROUTE. Turn screens show route decision, source, fallback count and final provider; the home shows health and fallback totals. All configuration remains assistant.admin-only, with AI exposure NONE.

P-d: data-driven budgets

Requires QQQ 4.1.0-RC.5 for the core Alerts API and alert type metadata.

Create assistant_budget, assistant_budget_override, assistant_budget_event and assistant_budget_usage using the P-d DDL in schema-postgres.sql before deploying. The event unique key is required: it deduplicates each state per budget, scope and UTC period across replicas. Do not remove it or truncate events during a period.

Budgets appear in Assistant navigation. Overrides and immutable events are child lists. All four tables require assistant.admin and have AI exposure NONE. Overrides record the actual session grantor and require a reason and expiry. The standard host QQQ audit configuration applies to budget and override edits. assistant_budget_usage is a private accounting projection populated from persisted usage writes. It copies only counters, time, user, provider and model, with no owner lock, so shared INSTANCE/PROVIDER/MODEL sums can run under the caller's own session. The source usage table retains its owner locks. The projection is read-only, has no nav entry and no AI exposure. Pause usage writes during migration and run the DDL's backfill before restarting; otherwise shared totals would omit pre-upgrade usage. Keep accounting rows through their budget periods even if detail rows are removed. ROLE budgets give each permission holder a separate allowance, checked with their current session. Override scopeValue, when set, narrows the allowance to one user id.

Existing policy/config budgets and provider monthly caps remain additional limits; an override of a budget row does not waive those existing caps.

Provider and MODEL scope values are numeric provider/model registry row ids; USER is a user id and ROLE is a permission name. INSTANCE has no scope value. Periods reset at midnight UTC or the first of the month UTC. Token usage includes input, output, cache-read and cache-write tokens; USD uses the recorded micro-dollar cost, not today's model price. No default budget rows are seeded.

P-c: classification, PHI routing and encrypted storage

Apply these additive schema changes before deploying P-c (adjust column mappings for your backend). New conversations/turns record PUBLIC, CONFIDENTIAL, PII or PHI; legacy null classifications read as PUBLIC. Backfill known historical PHI conversations to PHI before allowing their history to be used with a provider.

ALTER TABLE assistant_conversation ADD COLUMN max_classification VARCHAR(16);
ALTER TABLE assistant_turn ADD COLUMN classification VARCHAR(16);
ALTER TABLE assistant_turn ADD COLUMN classification_tables VARCHAR(500);
ALTER TABLE assistant_conversation ALTER COLUMN title TYPE TEXT;
ALTER TABLE assistant_tool_call ALTER COLUMN result_summary TYPE TEXT;
ALTER TABLE assistant_pending_action ALTER COLUMN summary TYPE TEXT;
ALTER TABLE assistant_pending_action ALTER COLUMN result_summary TYPE TEXT;
CREATE TABLE assistant_data_audit
(
   id SERIAL PRIMARY KEY,
   user_id VARCHAR(255),
   conversation_id INTEGER,
   turn_id INTEGER,
   provider_id INTEGER,
   provider_name VARCHAR(200),
   model VARCHAR(200),
   hipaa_eligible BOOLEAN,
   classification VARCHAR(16),
   tables VARCHAR(500),
   decision VARCHAR(16),
   reason VARCHAR(1000),
   create_date TIMESTAMP WITH TIME ZONE
);
CREATE INDEX assistant_data_audit_user_id ON assistant_data_audit (user_id);
CREATE INDEX assistant_data_audit_conversation_id ON assistant_data_audit (conversation_id);
CREATE INDEX assistant_data_audit_turn_id ON assistant_data_audit (turn_id);

assistant_data_audit is an admin read-only, owner-locked child list on a conversation, outside navigation. It records each model call carrying PII/PHI and each routing block, without record values. It expires with the conversation. PHI conversations use hipaaRetentionDays (2190 by default), even outside HIPAA mode; null uses normal retention. Existing tool-result/replay cleanup still uses toolResultRetentionDays.

Before enabling HIPAA mode, mark the permitted provider rows hipaaEligible = true and provision conversationEncryptionKeyEnvName with a base64-encoded 32-byte key. Startup refuses missing or invalid keys. A code-only provider is not eligible. The flag records the operator's eligibility decision; it does not establish a BAA.

New/updated message parts, titles, conversation context, tool payloads/redactions and summaries, replay data, and pending-action payloads/summaries use enc:v1:<base64(iv|ciphertext|tag)> (AES-256-GCM, random 12-byte IV, 16-byte tag). Only AssistantStore decrypts; direct backend/admin record views show ciphertext. String columns holding encrypted summaries/titles must be TEXT to allow expansion. Legacy plaintext reads are supported; existing rows are not automatically rewritten. For an existing deployment, migrate legacy payloads through AssistantStore under the appropriate owner/admin session before treating the whole archive as encrypted. Keep the key available for the lifetime of encrypted data and backups. Changing the key requires a controlled decrypt/re-encrypt migration; P-c has no key rotation job. Corrupt ciphertext, a wrong key, and encrypted data without a key fail closed.

0.1.0-RC.3 to 0.1.0-RC.4: provider settings, prices, per-call usage, app home

Three new tables. Create them before you deploy (see docs/schema-postgres.sql, which also seeds the Anthropic prices):

  • assistant_provider (with the api_key_status column: it is filled on read and never written, but the backend selects it)
  • assistant_model_price
  • assistant_usage

Then, to keep answering exactly as before:

  1. Register the provider type: config.withProviderType(new AnthropicProviderType()). Keep withModelProvider(AnthropicModelProvider.fromEnvironment()) as the fallback.
  2. Add one provider row (or seed it in your migration): type anthropic, model = your current main model, enabled, default, apiKeyEnvName = ANTHROPIC_API_KEY (or leave it empty for the default). No key value is ever written.
  3. Run Test connection on it.

Until a row exists, the provider registered in code answers, as in RC.3. Other changes: the app label is Assistant; the nav no longer lists the retention process (it is a table action on conversations) or the audit tables (they are child lists). Grants on assistant.admin cover everything new.

0.1.0-RC.1 to 0.1.0-RC.3: snake_case names

The rename shipped in 0.1.0-RC.3. 0.1.0-RC.2 on Maven Central is an accidental republish of the RC.1 code (see CHANGELOG.md), so upgrading from RC.2 is the same as upgrading from RC.1.

RC.3 applies the QRun-IO naming rule (owner decision, 2026-10-12): "table names should always be lowercase and use _, and qbits should always be prefixed with their name". The QQQ table names, the owner security key type and the possible-value source change. Permissions do not. This is a metadata-only change: no table, column or index in your database changes, and no Liquibase or DDL step is needed.

What changed

Kind RC.1 (and RC.2) name RC.3 name
Table assistantConversation assistant_conversation
Table assistantMessage assistant_message
Table assistantTurn assistant_turn
Table assistantTurnEvent assistant_turn_event
Table assistantToolCall assistant_tool_call
Table assistantFeedback assistant_feedback
Table assistantPolicy assistant_policy
Table assistantUsageDaily assistant_usage_daily
Possible-value source (conversations) assistantConversation assistant_conversation
Permissions assistant.hasAccess, assistant.admin unchanged (QQQ's standard <base>.hasAccess suffix; the assistant base already follows the rule)
Security key type assistantUserId assistant_user_id
All-access key assistantUserIdAllAccess assistant_user_id_all_access

Unchanged: Java class names, field names (userId, conversationId, ...), the assistantAdmin app, the assistantRetention process, the /qqq/v1/assistant routes and the assistant supplemental instance meta-data.

The old names stay available as @Deprecated constants for one release, so migration code can refer to them: <Entity>.LEGACY_TABLE_NAME, AssistantQBitConfig.LEGACY_DEFAULT_OWNER_KEY_TYPE and AssistantMetaDataBuilder.LEGACY_ALL_ACCESS_SUFFIX. They will be removed in 0.2.0.

Physical tables

docs/schema-postgres.sql already creates assistant_* tables. If your RDBMS backend mapped the old QQQ names to those tables (for example with inferBackendName), the new QQQ names now equal the physical names and nothing else is needed; the mapping becomes a no-op. If you created tables literally named assistantConversation and so on, either rename them:

ALTER TABLE "assistantConversation" RENAME TO assistant_conversation;
ALTER TABLE "assistantMessage"      RENAME TO assistant_message;
ALTER TABLE "assistantTurn"         RENAME TO assistant_turn;
ALTER TABLE "assistantTurnEvent"    RENAME TO assistant_turn_event;
ALTER TABLE "assistantToolCall"     RENAME TO assistant_tool_call;
ALTER TABLE "assistantFeedback"     RENAME TO assistant_feedback;
ALTER TABLE "assistantPolicy"       RENAME TO assistant_policy;
ALTER TABLE "assistantUsageDaily"   RENAME TO assistant_usage_daily;

or keep them and set RDBMSTableBackendDetails.tableName to the old name in your table customizer.

Stored references to update

QQQ stores table names, permission names and key names outside this qbit. Update them in the same deployment. The examples are for Postgres with QQQ's default snake_case backend names; adjust the table and column names to your schema.

  1. Security keys. Permission grants (assistant.hasAccess, assistant.admin) need no change. If your authentication customizer grants the all-access key itself rather than calling AssistantSecurity.applySessionKeys, grant assistant_user_id_all_access. If you set ownerSecurityKeyType explicitly, the all-access key is now <keyType>_all_access.

  2. Saved views and saved reports on the assistant tables:

    UPDATE saved_view SET table_name = CASE table_name
       WHEN 'assistantConversation' THEN 'assistant_conversation'
       WHEN 'assistantMessage'      THEN 'assistant_message'
       WHEN 'assistantTurn'         THEN 'assistant_turn'
       WHEN 'assistantTurnEvent'    THEN 'assistant_turn_event'
       WHEN 'assistantToolCall'     THEN 'assistant_tool_call'
       WHEN 'assistantFeedback'     THEN 'assistant_feedback'
       WHEN 'assistantPolicy'       THEN 'assistant_policy'
       WHEN 'assistantUsageDaily'   THEN 'assistant_usage_daily'
       ELSE table_name END
     WHERE table_name IN ('assistantConversation', 'assistantMessage', 'assistantTurn', 'assistantTurnEvent',
                          'assistantToolCall', 'assistantFeedback', 'assistantPolicy', 'assistantUsageDaily');
    

    Run the same statement against saved_report (and quick_saved_view / shared_saved_view only if they store a table name in your schema).

  3. Audit table names. QQQ audits reference tables through the audit_table lookup. Rename the rows so old and new audits stay under one table:

    UPDATE audit_table SET name = CASE name
       WHEN 'assistantConversation' THEN 'assistant_conversation'
       WHEN 'assistantMessage'      THEN 'assistant_message'
       WHEN 'assistantTurn'         THEN 'assistant_turn'
       WHEN 'assistantTurnEvent'    THEN 'assistant_turn_event'
       WHEN 'assistantToolCall'     THEN 'assistant_tool_call'
       WHEN 'assistantFeedback'     THEN 'assistant_feedback'
       WHEN 'assistantPolicy'       THEN 'assistant_policy'
       WHEN 'assistantUsageDaily'   THEN 'assistant_usage_daily'
       ELSE name END
     WHERE name IN ('assistantConversation', 'assistantMessage', 'assistantTurn', 'assistantTurnEvent',
                    'assistantToolCall', 'assistantFeedback', 'assistantPolicy', 'assistantUsageDaily');
    

    If a new-name row was already created (the app ran RC.3 before this update), repoint audit.audit_table_id from the old row to the new one first, then delete the old row.

  4. Anything else that stores a table name: frontend links or bookmarks to /app/.../assistantConversation, Quick Search index rows (quick_search_index.table_name; reindex instead of editing), ESB topics or reports you configured by table name.

Order

Deploy RC.3 and run the statements above in the same maintenance window. Until the update runs, saved views and reports on the assistant tables do not open, and admins who get the all-access key from their own customizer only see their own rows.

0.1.0-RC.6: tools through the UI API

Database (see docs/schema-postgres.sql):

  • new tables assistant_data_access, assistant_pending_action, assistant_profile, assistant_skill and assistant_profile_skill;
  • new columns assistant_tool_call.ui_api_operation, http_status, audit_href, pending_action_id;
  • new columns assistant_policy.writes_require_confirmation, allowed_tools, max_rows_per_call, redact_pii.

Runtime: the tools call this server's own /qqq/v1 on 127.0.0.1 and the port the first assistant request arrived on. If the server is not reachable that way (a different bind address, a TLS-only listener), set withUiApiBaseUrl(...). Grant assistant.admin holders the new tables through the qbit's existing admin permission (no new permission names).

Behavior: nothing is writable until an admin adds a READ_WRITE data access row; before that the assistant reads exactly what it did before, now through the UI API.

P-a: provider and model registry

Before starting the new version, database-backed hosts add the provider columns and model sub-table below. Memory-backed hosts need no migration. Existing rows derive kind and location from the adapter and keep their model behavior until models are registered. Keys remain environment-variable references.

ALTER TABLE assistant_provider
   ADD COLUMN kind VARCHAR(30),
   ADD COLUMN tls_ca_bundle VARCHAR(100),
   ADD COLUMN location VARCHAR(10),
   ADD COLUMN hipaa_eligible BOOLEAN DEFAULT FALSE,
   ADD COLUMN region VARCHAR(50),
   ADD COLUMN api_version VARCHAR(30),
   ADD COLUMN connect_timeout_seconds INTEGER DEFAULT 10 CHECK (connect_timeout_seconds BETWEEN 1 AND 600),
   ADD COLUMN request_timeout_seconds INTEGER DEFAULT 120 CHECK (request_timeout_seconds BETWEEN 1 AND 600),
   ADD COLUMN last_discovery_at TIMESTAMP WITH TIME ZONE,
   ADD COLUMN last_discovery_status VARCHAR(16),
   ADD COLUMN last_discovery_error VARCHAR(500);
ALTER TABLE assistant_provider ALTER COLUMN model TYPE VARCHAR(200);
ALTER TABLE assistant_provider ALTER COLUMN last_test_model TYPE VARCHAR(200);
ALTER TABLE assistant_usage ALTER COLUMN model TYPE VARCHAR(200);
ALTER TABLE assistant_message ALTER COLUMN model TYPE VARCHAR(200);
ALTER TABLE assistant_turn ALTER COLUMN model TYPE VARCHAR(200);

CREATE TABLE assistant_model
(
   id SERIAL PRIMARY KEY,
   provider_id INTEGER NOT NULL REFERENCES assistant_provider(id),
   model_id VARCHAR(200) NOT NULL,
   display_name VARCHAR(200),
   enabled BOOLEAN,
   supports_tools BOOLEAN,
   supports_vision BOOLEAN,
   supports_streaming BOOLEAN,
   supports_reasoning_effort BOOLEAN,
   context_window_tokens INTEGER,
   max_output_tokens INTEGER,
   input_usd_per_m_tok NUMERIC(12, 4),
   output_usd_per_m_tok NUMERIC(12, 4),
   source VARCHAR(16),
   discovered_at TIMESTAMP WITH TIME ZONE,
   last_test_at TIMESTAMP WITH TIME ZONE,
   last_test_status VARCHAR(16),
   last_test_latency_ms INTEGER,
   last_test_error VARCHAR(500),
   create_date TIMESTAMP WITH TIME ZONE,
   modify_date TIMESTAMP WITH TIME ZONE,
   UNIQUE (provider_id, model_id)
);

Register the optional qbit-assistant-provider-openai module and OpenAiCompatibleProviderType to offer its kinds. Azure and generic endpoints require an explicit allowedBaseUrls entry; local defaults may be keyless. Discover models preserves existing rows. Cloud catalogs larger than 20 are added disabled; enable the models to offer before leaving the provider's model empty. HIPAA eligibility records an administrator's fact; P-a does not implement PHI routing.

Custom CA bundles are server configuration, never database content. Register them with withTrustedCaBundle(name, pem) or withTrustedCaBundleFile(name, path) at startup, then set the HTTPS provider row's tlsCaBundle to that name. Empty references retain JVM default trust. The bundle adds trust without replacing JVM roots; hostname verification remains enabled. If the P-a columns were already applied, add only tls_ca_bundle VARCHAR(100) to assistant_provider.

P-b: AWS Bedrock and Google Vertex

Before starting this version, database-backed hosts that already applied P-a add:

ALTER TABLE assistant_provider ADD COLUMN cloud_project VARCHAR(100);

Fresh installations use docs/schema-postgres.sql; memory-backed hosts need no migration. Existing providers leave the new column null. Vertex requires the GCP project id in cloudProject; Bedrock and Vertex both require region and reject apiKeyEnvName. They default to CLOUD; hipaaEligible remains a manual admin fact.

Add the optional qbit-assistant-provider-bedrock and/or qbit-assistant-provider-vertex module at the QBit version and register BedrockProviderType / VertexProviderType. Credentials come from the server's AWS default chain / Google Application Default Credentials. No credentials are stored in provider rows. Hosts using AWS IRSA or web identity supply software.amazon.awssdk:sts on their own runtime classpath (matching the transitive AWS SDK version); this QBit does not add it. See README Providers for configuration, endpoint overrides and static discovery limitations.