Merge develop into release/0.1 for the next 0.1.0 RC #87
Open
gitea-actions Bot
wants to merge 14 commits from
develop into release/0.1
pull from: develop
merge into: QRun-IO:release/0.1
QRun-IO:feat/process-tools
QRun-IO:feat/writes-follow-the-person
QRun-IO:fix/atomic-confirm-and-closed-surface
QRun-IO:fix/tool-service-owns-policy-and-identity
QRun-IO:fix/tool-call-service-enforces-its-checks
QRun-IO:feat/tool-registry
QRun-IO:docs/actions-and-mcp-design
QRun-IO:release/0.1
QRun-IO:rc/develop-into-release-0.1-everyone
QRun-IO:feat/store-reads-as-trusted-code
QRun-IO:rc/develop-into-release-0.1-weekly
QRun-IO:feat/weekly-budget
QRun-IO:rc/develop-into-release-0.1-save
QRun-IO:feat/save-and-schedule-analysis
QRun-IO:rc/develop-into-release-0.1-analyze
QRun-IO:feat/analyze-records
QRun-IO:fix/turn-watchdog-never-hangs-develop
QRun-IO:fix/turn-watchdog-never-hangs
QRun-IO:fix/live-thread-placeholders
QRun-IO:merge/develop-into-release-0.1
QRun-IO:automation/qbit-rc-pending
QRun-IO:fix/transcript-stored-before-message-end-develop
QRun-IO:fix/transcript-stored-before-message-end
QRun-IO:main
QRun-IO:archive/qrun-github-cutover-20261101/ca9212731dc2/develop
QRun-IO:feat/openai-compatible-provider
QRun-IO:dependabot/maven/develop/maven-minor-and-patch-634f43d868
QRun-IO:dependabot/github_actions/develop/github-actions-0fdda1077f
QRun-IO:feature/orb-0.8-track
QRun-IO:feature/orb-0.7.18
QRun-IO:feature/orb-0.7.17
QRun-IO:feature/orb-standardize-release-0.1
QRun-IO:feature/orb-main-standardization
QRun-IO:feature/shared-codeql
QRun-IO:feature/orb-0712-stable-gate
QRun-IO:feature/orb-rc-worker
QRun-IO:feature/orb-0.7.11
QRun-IO:feature/central-ci
QRun-IO:feature/conversation-management
QRun-IO:feature/starter-prompts-from-access
QRun-IO:feature/turns-not-jobs
QRun-IO:feature/assistant-attachments
QRun-IO:feature/assistant-optional-provider-health
QRun-IO:fix/assistant-health-tile-wording
QRun-IO:feature/fail-soft-renders
QRun-IO:fix/records-render-and-stream-context
QRun-IO:ci/release-rc-secrets-inherit
QRun-IO:feature/ui-api-1.6
QRun-IO:fix/shutdown-hook-weak-runtime
QRun-IO:fix/help-bundle-version
QRun-IO:feature/health-checks
QRun-IO:feature/jobs-tray
QRun-IO:feature/help-bundle
QRun-IO:feature/transcript-redaction
QRun-IO:feature/foreign-keys
QRun-IO:feature/schema-indexes
QRun-IO:fix/assistant-defaults-rollback
QRun-IO:feature/durable-turns
QRun-IO:fix/assistant-defaults-guards
QRun-IO:feature/tables-populated-defaults
QRun-IO:feature/provider-spi-package
QRun-IO:feature/email-api
QRun-IO:feature/orb-0.6.9
QRun-IO:feature/write-mode
QRun-IO:feature/x1-y1-channel-wall
QRun-IO:feature/email-qbit
QRun-IO:feature/spec-endpoints-core-invoker
QRun-IO:feature/provider-api-module
QRun-IO:feature/p7-scheduled-tasks
QRun-IO:feature/bulk-load-tools
QRun-IO:feature/p6-rag
QRun-IO:feature/presentation-demo-shots
QRun-IO:feature/answer-presentation
QRun-IO:fix/core-alerts-rc5
QRun-IO:feature/p5-routing-fallback
QRun-IO:feature/p4-budgets
QRun-IO:feature/p3-hipaa-routing
QRun-IO:feature/p2-bedrock-vertex
QRun-IO:feature/p1-provider-registry
QRun-IO:feature/spec-providers-write-mode-wall
QRun-IO:feature/demo-pending-card
QRun-IO:feature/release-rc-automation
QRun-IO:feature/demo-screens
QRun-IO:feature/profiles-skills-access
QRun-IO:feature/ui-api-tools
QRun-IO:feature/strict-tools-grammar-fix
QRun-IO:feature/app-home-settings
QRun-IO:feature/fix-rename-version-labels
QRun-IO:feature/note-burned-rc
QRun-IO:feature/assistant-record-screens
QRun-IO:feature/robustness-test-flake
QRun-IO:feature/ga-only-release-tags
QRun-IO:feature/snake-case-table-names
QRun-IO:chore/qrun-repo-standard
QRun-IO:feature/pom-revision-comment
QRun-IO:feature/phase-0-1-assistant
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1719147114
|
Merge pull request 'feat(access): writes follow the signed-in person; no answer without a spending limit; a fourth review of the tool boundary closed' (#109) from feat/writes-follow-the-person into develop | ||
|
|
cc5fe2a700
|
feat(access): writes follow the signed-in person; no answer without a spending limit; fix: a fourth review of the tool boundary
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>
|
||
|
|
f1cfea7734
|
Merge the actions and MCP design (docs/actions-and-mcp-design) into feat/writes-follow-the-person
The design this branch builds package A2 of. It is merged here so that the sentences of the design that the code and the owner's decisions of 2026-11-12 have overtaken can be corrected in the same change. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |
||
|
|
ad6d854d22
|
Merge pull request 'fix(tools): one decision per pending action, a tool surface closed at compile time; pending actions with before and after' (#108) from fix/atomic-confirm-and-closed-surface into develop | ||
|
|
0dc325cd2c
|
fix(tools): one decision per pending action, a tool surface closed at compile time; feat: pending actions with before and after
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> |
||
|
|
3802c5be6c
|
Merge pull request 'fix(tools): the tool-call service owns the policy, the identity and the confirmation' (#107) from fix/tool-service-owns-policy-and-identity into develop | ||
|
|
7c9176fcc8
|
fix(tools): the tool-call service owns the policy, the identity and the confirmation
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>
|
||
|
|
441e8218fd
|
Merge pull request 'fix(tools): the tool-call service makes its own checks, for any caller' (#106) from fix/tool-call-service-enforces-its-checks into develop | ||
|
|
d42c536dcf
|
fix(tools): the tool-call service makes its own checks, for any caller
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>
|
||
|
|
21f634c167
|
Merge pull request 'refactor(tools): one tool registry (no behaviour change); build against QQQ 4.1.0-RC.32' (#105) from feat/tool-registry into develop | ||
|
|
f035d6356b
|
refactor(tools): one tool registry, no behaviour change
Every tool is registered once in tools.registry.ToolRegistry: its name, its
kind (READ, PROPOSE, DISPLAY), the checks it runs, its definition and how one
call of it runs. The chat's tool list, a tool call of a turn and a confirmed
write all look a tool up there; the turn engine and the confirm route no
longer choose a tool family with an if-chain.
"Run one tool call for this person under this policy, and log it" moves
out of TurnEngine into engine.ToolCallService, which the confirm route uses
too: the refusal of a tool that is not offered, the call (with the person's
own session) and the assistant_tool_call row.
What moved where:
- ContextBuilder.tools: the per-family blocks become
ToolRegistry.definitions(ToolOffer); each family's block is its
AssistantTool.definition (UiApiToolFamily, engine.KernelToolFamily,
KnowledgeToolFamily, PresentResultsToolFamily, BulkLoadToolFamily).
isStrict moves to ToolRegistry.isStrict.
- TurnEngine.executeTools: the 'Unknown tool' refusal is
ToolCallService.refusal; the dispatch is ToolCallService.execute; each
branch is its family's AssistantTool.execute (the data families keep the
QContext init and clear around the call; the presenter stays one per turn
through ToolCallScope.state).
- TurnEngine.recordToolResult: the row is built by ToolCallService.log; the
bulk-load log reduction and search_knowledge's operation are the tools'
forLog and operationWithoutDetail.
- AssistantApi.confirmAction: the if-chain is
ToolCallService.executeConfirmed; the row is ToolCallService.logConfirmed.
UiApiToolbox, AnalysisTools, BulkLoadTools, KnowledgeTool, PresentResultsTool
and the core kernel keep all of their behaviour. A tool family is a class
under com.kingsrook.qbits.assistant.tools that implements ToolFamily; it is
found at startup, so a new family is new files only. The kernel family lives
in engine and is named by the runtime, because nothing under tools may touch
the kernel (ArchitectureTest).
Tests: ChatToolSetTest compares the chat's tool list (names, order,
descriptions, schemas, strict flags, in six scenarios) with the list written
from origin/develop at
|
||
|
|
f848092759
|
build: build against QQQ 4.1.0-RC.32
Moves qqq.snapshot.version from 4.1.0-RC.25 to 4.1.0-RC.32, the version applications (GG Forge) pin. No behaviour change. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |
||
|
|
694309f528
|
docs(design): name the open write-mode draft the design would replace
The write-mode switch is not on develop but exists as an open draft pull request (#21). Say so, and say what happens to it if the owner confirms that every write is confirmed. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |
||
|
|
798acd5541
|
docs(design): actions and MCP, the assistant does what the signed-in person may do
Draft design and work plan for owner review. Nothing is implemented here. - Writes follow the person's permissions by default: data access defaults declared in code, an administrator can only restrict, every write is confirmed on a card that shows the values before and after. - New tools: run a process the person may run, create and edit saved reports and saved analyses, schedule delivery by email. - An MCP endpoint that serves the same tool registry as chat: read-only first, then writes as proposals approved in the application. - Test strategy, repositories and release order, risks, decisions for the owner, and work packages that can be built in parallel. docs/DESIGN.md gets a pointer under "Next phases". Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> |