feat(process): the assistant runs a process the signed-in person may run, confirmed on a card; four review findings closed #110

Open
james.maes wants to merge 2 commits from feat/process-tools into develop
pull from: feat/process-tools
Owner

Builds package A4 of the actions design (docs/actions-and-mcp.md, section 4 and 12.2): the assistant can run a process the signed-in person may run themselves, after that person confirmed the run on a card. With it, four findings of the last independent review, each described by the guarantee it gives, and the one test that review asked for.

One behaviour change needs a host's attention before deploying: a table's own bulk edit and bulk delete become runnable through the assistant, and any other process once the application names it on its reviewed list. UPGRADE.md says what to do.

An independent review of this pull request followed; what changed after it is in "Changes after the independent review" below, and the rest of this description is brought up to date with it.

The rules this package keeps

  • Every tool call runs as the signed-in person, with their current permissions and budget, never as the assistant. The assistant may do what the person may do, no more.
  • A process run changes things, so it is shown on a confirmation card and confirmed by the person first. Nothing switches that off.
  • An administrator's setting can only narrow. The default lives in versioned code.

What a person can now ask for, and what they see

  1. "Publish the two draft posts." The assistant calls list_processes and describe_process: the processes this person may run and their inputs, from the person's own metadata (asked as them through the UI API). A process they may not run is never listed or described, and by name it is refused in the words used for a process that does not exist.
  2. A required input the person did not give is asked for. No value is ever made up.
  3. run_process proposes the run. Before a card exists the inputs are checked against the fields the process's screens declare: an unknown input, a value of the wrong type and a missing required input are refused. The records are found as the person and fixed then, by primary key (a filter that finds more than 500 is refused, for the person to narrow).
  4. The card shows the process, every input, what the first screen says, the records (how many, of which table, and which: the ids, or the filter in words), how it runs and the steps that will stop for the person. Nothing has run.
  5. The person confirms. The run starts through the UI API with the confirming request's own session: a permission revoked since the proposal refuses it with the application's own answer. The run is over exactly the records its card counted, and does not start when one of them is gone.
  6. The run goes on until it ends (its summary lines come back) or reaches a screen with something for the person that their card did not show: a review (nothing is stored yet), a form, values to read, a text, a list of records. It stops there, and going on takes a second card that shows what that screen shows.

What is never runnable through the assistant

Decided in one place, ProcessRunnability, shared by the tools and the host's reviewed list:

  1. a process denied to the assistant client channel: by its author (QPermissionRules.withDeniedClients(Set.of("assistant"))), by the application (ClientChannelsMetaData) or by core's default patterns;
  2. the assistant's own processes, and any process whose data access is not READ_WRITE (a pin or default in code, or an administrator's row);
  3. a hidden process, except a table's own bulk edit and bulk delete;
  4. a process no screen offers (in no app, and no table's action);
  5. a process on a table the assistant may not change;
  6. a process with a screen the assistant cannot present (a file upload, a required password, a download, a bulk-load form, a folder picker, a widget, custom content, an unknown component). The person is told why and where to run it themselves;
  7. a process that is not on the application's reviewed list, unless it is a table's own bulk edit or bulk delete.

The default in code is unchanged (defaultProcessAccess is READ_WRITE); it is a ceiling. Which processes run under it is said by the application's registered reviewed list. An application that registered none has two kinds of runnable process: a table's own bulk edit and bulk delete.

A long-running process

  • A durable job, when the application reports the process as eligible: the confirmed run creates the job and never starts it. Core creates a job launched from the assistant channel waiting for the person and refuses to start it from that channel (DurableJobs.launch and control, read at the QQQ version this repository builds against). The assistant calls only the launch, and sends no expected row count, so core's "small assistant jobs start by themselves" setting never applies either. The person starts the job on its job record, from their own session.
  • Any other process runs through its own init and step operations. A step that goes to the background is waited for briefly (15 seconds); after that the run is reported as still running.
  • Either way the pending action ends when the run was started, not when it ends: no action is held RUNNING for a job's duration (retention fails an action left RUNNING for over an hour). get_run_status reports where a run or a job stands, and never moves one on.

The reviewed list (finding 1)

What runs is decided when the application runs. The application registers the processes it reviewed for the assistant in its code:

new AssistantQBitConfig().withReviewedProcessesResource("assistant-runnable-processes.txt")   // or withReviewedProcesses(List.of(...))
The application registered The assistant runs, for a person who may run it themselves
no list a table's own bulk edit and bulk delete, and no other process
a list (an empty one is a list) exactly the listed processes, and those two

The list names; it grants nothing: rules 1 to 6, an administrator's narrowing row and the person's own permission hold for a listed process as for any other, and nothing in the database adds a process.

The build-time helper stays, as the way an application notices drift:

RunnableProcessReview.assertReviewed(qInstance, person, Path.of("src/main/resources/assistant-runnable-processes.txt"));

person is a session with the permissions of the roles to review, or null for a person who holds every permission. The test fails, naming each process, when a process could be listed and is not, when a listed process is gone, and when the file is not the list the application registered. It needs the instance with the QBit produced, and no server or database.

The review findings

Guarantee Test
1 Only a process the application reviewed runs (and a table's own bulk edit and bulk delete), and an application notices drift between its list and its processes. ProcessToolsTest.theProcessToolsAreOfferedOnlyToAPersonWhoMayRunAProcess, onlyAReviewedProcessOrATablesOwnBulkActionRuns, theReviewHelperFailsWhenTheListAndTheApplicationDrift
2 An administrator is warned, before upgrading, about a table that the data-access migration will open, with a query that has the migration's own predicate. DataAccessFollowUserChangelogTest.theQueryInTheUpgradeNotesFindsExactlyTheRowsTheChangesetRemoves (runs the query from UPGRADE.md and the changeset on the same rows)
3 No tool tells the model that confirmation can be turned off. ChatToolSetTest
4 No model is called for a person with no spending limit in force: the routing classifier now asks for its own model. The administrator's Test connection is the one exemption, decided and documented with what bounds it. RoutingFallbackTest.classifierIsNotAskedWithNoSpendingLimitInForce (seen failing with the check taken out), AssistantSettingsTest.theConnectionTestNeedsNoSpendingLimitAndIsBounded
+ A confirmed update that would move a record into a record-security key the person lacks is refused by the application, with its own message, and nothing changes. WritesFollowThePersonTest.anUpdateIntoARecordSecurityKeyThePersonLacksIsRefusedAtConfirmAndNothingChanges

The cached request prefix changes once

The tool list is part of the cached prefix. The stored file the chat tool-set test compares against was compared with the new one line by line; it differs in exactly two ways.

Before, for a person who may change a table (24 tools):

analyze_records, bulk_file_attach, bulk_load_dry_run, bulk_load_run, bulk_mapping_suggest, bulk_profile_describe, bulk_profile_save, bulk_profiles_list, count_records, create_record, delete_record, describe_table, get_record, get_record_history, list_tables, navigate, present_results, query_records, render_widget, save_analysis, search_help, search_knowledge, search_records, update_record

After (28): the same, plus describe_process, get_run_status, list_processes, run_process, each in its sorted place. Every line that was there is byte for byte what it was, except that the descriptions of create_record, update_record and delete_record no longer end "unless the administrator turned confirmation off".

For a person who may run no process (10 tools, before and after, byte for byte):

count_records, describe_table, get_record, list_tables, navigate, present_results, query_records, render_widget, search_help, search_records

The process tools appear only for a person who may run at least one process through the assistant, and not when the policy leaves them out. The host of that test registered no reviewed list, so the person who may change tables is offered the four (the tables' bulk actions), and the person of the scenario everythingAsAnotherPerson, who may run processes of another QBit and change no table, is not: that scenario's lines in the stored file are without the four process tools (the one expectation of ChatToolSetTest that changed after the review).

Where the code differs from the design

Recorded in docs/actions-and-mcp.md, section 4.5. In short: a process the person may not run is refused as if it did not exist (not with the operation's "permission denied"); two more kinds of process are never runnable (no screen offers it; its table is read-only for the assistant); a download is not drivable (only links into the application are emitted); the second card at a review is proposed with the run's reference instead of being created by the confirmation; a run over records is refused at confirmation when one of its records is gone; possible values of an input are not listed. After the review: the reviewed list is enforced when the application runs, not only in its build; a field whose name a process keeps for itself is not an input; a run stops at every later screen that has something to show; the records of a run are fixed at the proposal; a card to go on carries the run's version; a process that may run as a job is never run another way; text of the application reaches a model only inside the data envelope.

Also

  • A scheduled task is offered run_process only with its write approval.
  • UI API extension assistant 1.10.0, additive: AssistantActionV1.processName, processLabel; changes carries the card's lines for a process_run. A released panel draws the card as a record change (it knows only the three record tools); the run works.
  • AssistantStore is still the only code that calls data actions. No schedule or report tool (that is A5).

Not closed

  • The panel's process card and "start the job in the same click" are the panel's package. Until then a durable job waits for the person on its job record.
  • The possible values of a process input are not listed to the model.
  • A run that stopped or is still working is kept in the memory of the server that started it (as core keeps the run): another replica, or a restart, does not know it.

Changes after the independent review

The review's verdict was "fix first". Each change is described by the guarantee it gives, with the test that shows it.

Guarantee Test
1 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: the inputs the process's backend steps declare, and a fixed set this QBit owns (ProcessControlNames.RESERVED). describe_process lists such a field under cannotBeSetThroughTheAssistant, a value for it is refused before a card exists and again at confirmation, and the process stays runnable for its other fields. ProcessRequestsTest.aFieldWhoseNameTheProcessKeepsForItselfIsNeverSent (a fixture table with an editable field named sourceTable: every request of the run is read, and none carries a value under that name), ProcessControlNamesTest
2 A person confirms what they were shown. A run stops at every later screen that has something to show: a form (also with no required field), values to read, a text, a list of records. Going on is a second card that shows what the screen shows, as the server read it. Only a screen with nothing to show is passed; the screen a run starts at is on the first card. ProcessToolsTest.aRunStopsAtALaterScreenWithATextUntilACardShowedIt, aRunStopsAtALaterFormWhoseValuesAStepComputed, aRunStopsAtALaterScreenWithValuesToRead, aRunStopsAtALaterScreenThatListsRecords, aScreenWithNothingToShowIsPassedAndTheFirstScreenIsOnTheFirstCard
3 Only a reviewed process runs, decided when the application runs. See "The reviewed list" above. ProcessToolsTest.onlyAReviewedProcessOrATablesOwnBulkActionRuns (no list, an empty list, a list that also names processes a rule keeps away, an administrator's row that adds nothing, a card made before the list was taken away), theReviewHelperFailsWhenTheListAndTheApplicationDrift
4a 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, never the filter. ProcessToolsTest.aRunIsOverExactlyTheRecordsItsCardCounted, aFilterThatFindsMoreThanTheCapIsRefused, ProcessRequestsTest.aRunProposedWithAFilterIsSentWithTheRecordsOfItsCard
4b 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, nothing is proposed and nothing runs on a confirmation. ProcessRequestsTest.aProcessThatMayRunAsAJobIsNeverRunOnConfirmationInstead
4c Text of the application reaches a model only as data, inside the 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. A person reads the same words as they are. ProcessToolsTest.whatTheApplicationSaidReachesAModelOnlyAsData (a conversation: propose, confirm, ask again; the next request is read whole)
4d 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; an older card of the same run is refused. ProcessToolsTest.aCardGoesOnOnlyWithTheRunAsItStoodWhenTheCardWasMade

The reserved names, and where they come from. ProcessControlNames.RESERVED was read from the QQQ version this repository builds against (4.1.0-RC.32): every name StreamedETLWithFrontendProcess, StreamedETLProcess, the streamed preview, validate and execute steps, ExtractViaQueryStep, the LoadVia... steps and the bulk edit and bulk delete steps read from or write to a run's values; every name RunProcessAction and RunBackendStepAction read from them; and the names the process operations carry a run's records, identity and timing in. ProcessControlNamesTest reflects over the constants those classes declare and over the inputs a streamed process's backend steps declare, and fails when the set lacks one, so a later QQQ that adds a name is noticed. The inputs of a process's own backend steps are added per process, from its metadata.

What changes for a person or a host, beyond the above.

  • A process of a host's own that declares a screen's field as an input of a backend step as well: that field is set in the application, not through the assistant. If it is required and has no default, the process is not run through the assistant, and the person is told where to run it.
  • A screen that lists records shows the first ten on the card and says how many more there are; a column that is a field the assistant may not see the values of is left out. Where the records cannot be shown (the application does not offer the operation that reads them, or they are of a table the assistant may not read), the run does not go on through the assistant.
  • A text part of a stored message may carry source (the tool whose outcome it repeats). A panel shows it as any text.

Kept true: ChatToolSetTest passes with one changed expectation (named above); ToolCallChecksTest passes unchanged; AssistantStore is still the only code that calls data actions; no new tool.

Verification

./mvnw --batch-mode --no-transfer-progress -s <settings> -T1C clean verify at a9ebe64: BUILD SUCCESS: the enforcer rules, the tests of all seven modules (600 in qbit-assistant, none failed or skipped) and the JaCoCo gate of each. That command runs no Checkstyle, SpotBugs or PMD in this repository.

🤖 Generated with Claude Code

Builds package **A4** of the actions design (`docs/actions-and-mcp.md`, section 4 and 12.2): the assistant can run a process the signed-in person may run themselves, after that person confirmed the run on a card. With it, four findings of the last independent review, each described by the guarantee it gives, and the one test that review asked for. **One behaviour change needs a host's attention before deploying:** a table's own bulk edit and bulk delete become runnable through the assistant, and any other process once the application names it on its reviewed list. `UPGRADE.md` says what to do. An independent review of this pull request followed; what changed after it is in "Changes after the independent review" below, and the rest of this description is brought up to date with it. ## The rules this package keeps - Every tool call runs as the signed-in person, with their current permissions and budget, never as the assistant. The assistant may do what the person may do, no more. - A process run changes things, so it is shown on a confirmation card and confirmed by the person first. Nothing switches that off. - An administrator's setting can only narrow. The default lives in versioned code. ## What a person can now ask for, and what they see 1. "Publish the two draft posts." The assistant calls `list_processes` and `describe_process`: the processes this person may run and their inputs, from the person's own metadata (asked as them through the UI API). A process they may not run is never listed or described, and by name it is refused in the words used for a process that does not exist. 2. A required input the person did not give is asked for. No value is ever made up. 3. `run_process` proposes the run. Before a card exists the inputs are checked against the fields the process's screens declare: an unknown input, a value of the wrong type and a missing required input are refused. The records are found as the person and fixed then, by primary key (a filter that finds more than 500 is refused, for the person to narrow). 4. The card shows the process, every input, what the first screen says, the records (how many, of which table, and which: the ids, or the filter in words), how it runs and the steps that will stop for the person. Nothing has run. 5. The person confirms. The run starts through the UI API with the confirming request's own session: a permission revoked since the proposal refuses it with the application's own answer. The run is over exactly the records its card counted, and does not start when one of them is gone. 6. The run goes on until it ends (its summary lines come back) or reaches a screen with something for the person that their card did not show: a review (nothing is stored yet), a form, values to read, a text, a list of records. It stops there, and going on takes a second card that shows what that screen shows. ## What is never runnable through the assistant Decided in one place, `ProcessRunnability`, shared by the tools and the host's reviewed list: 1. a process denied to the `assistant` client channel: by its author (`QPermissionRules.withDeniedClients(Set.of("assistant"))`), by the application (`ClientChannelsMetaData`) or by core's default patterns; 2. the assistant's own processes, and any process whose data access is not `READ_WRITE` (a pin or default in code, or an administrator's row); 3. a hidden process, except a table's own bulk edit and bulk delete; 4. a process no screen offers (in no app, and no table's action); 5. a process on a table the assistant may not change; 6. a process with a screen the assistant cannot present (a file upload, a required password, a download, a bulk-load form, a folder picker, a widget, custom content, an unknown component). The person is told why and where to run it themselves; 7. a process that is not on the application's reviewed list, unless it is a table's own bulk edit or bulk delete. **The default** in code is unchanged (`defaultProcessAccess` is `READ_WRITE`); it is a ceiling. Which processes run under it is said by the application's registered reviewed list. An application that registered none has two kinds of runnable process: a table's own bulk edit and bulk delete. ## A long-running process - **A durable job**, when the application reports the process as eligible: the confirmed run creates the job and never starts it. Core creates a job launched from the `assistant` channel waiting for the person and refuses to start it from that channel (`DurableJobs.launch` and `control`, read at the QQQ version this repository builds against). The assistant calls only the launch, and sends no expected row count, so core's "small assistant jobs start by themselves" setting never applies either. The person starts the job on its job record, from their own session. - **Any other process** runs through its own `init` and `step` operations. A step that goes to the background is waited for briefly (15 seconds); after that the run is reported as still running. - Either way **the pending action ends when the run was started**, not when it ends: no action is held `RUNNING` for a job's duration (retention fails an action left `RUNNING` for over an hour). `get_run_status` reports where a run or a job stands, and never moves one on. ## The reviewed list (finding 1) What runs is decided when the application runs. The application registers the processes it reviewed for the assistant in its code: ```java new AssistantQBitConfig().withReviewedProcessesResource("assistant-runnable-processes.txt") // or withReviewedProcesses(List.of(...)) ``` | The application registered | The assistant runs, for a person who may run it themselves | |---|---| | no list | a table's own bulk edit and bulk delete, and no other process | | a list (an empty one is a list) | exactly the listed processes, and those two | The list names; it grants nothing: rules 1 to 6, an administrator's narrowing row and the person's own permission hold for a listed process as for any other, and nothing in the database adds a process. The build-time helper stays, as the way an application notices drift: ```java RunnableProcessReview.assertReviewed(qInstance, person, Path.of("src/main/resources/assistant-runnable-processes.txt")); ``` `person` is a session with the permissions of the roles to review, or null for a person who holds every permission. The test fails, naming each process, when a process could be listed and is not, when a listed process is gone, and when the file is not the list the application registered. It needs the instance with the QBit produced, and no server or database. ## The review findings | | Guarantee | Test | |---|---|---| | 1 | Only a process the application reviewed runs (and a table's own bulk edit and bulk delete), and an application notices drift between its list and its processes. | `ProcessToolsTest.theProcessToolsAreOfferedOnlyToAPersonWhoMayRunAProcess`, `onlyAReviewedProcessOrATablesOwnBulkActionRuns`, `theReviewHelperFailsWhenTheListAndTheApplicationDrift` | | 2 | An administrator is warned, before upgrading, about a table that the data-access migration will open, with a query that has the migration's own predicate. | `DataAccessFollowUserChangelogTest.theQueryInTheUpgradeNotesFindsExactlyTheRowsTheChangesetRemoves` (runs the query from `UPGRADE.md` and the changeset on the same rows) | | 3 | No tool tells the model that confirmation can be turned off. | `ChatToolSetTest` | | 4 | No model is called for a person with no spending limit in force: the routing classifier now asks for its own model. The administrator's Test connection is the one exemption, decided and documented with what bounds it. | `RoutingFallbackTest.classifierIsNotAskedWithNoSpendingLimitInForce` (seen failing with the check taken out), `AssistantSettingsTest.theConnectionTestNeedsNoSpendingLimitAndIsBounded` | | + | A confirmed update that would move a record into a record-security key the person lacks is refused by the application, with its own message, and nothing changes. | `WritesFollowThePersonTest.anUpdateIntoARecordSecurityKeyThePersonLacksIsRefusedAtConfirmAndNothingChanges` | ## The cached request prefix changes once The tool list is part of the cached prefix. The stored file the chat tool-set test compares against was compared with the new one line by line; it differs in exactly two ways. Before, for a person who may change a table (24 tools): ``` analyze_records, bulk_file_attach, bulk_load_dry_run, bulk_load_run, bulk_mapping_suggest, bulk_profile_describe, bulk_profile_save, bulk_profiles_list, count_records, create_record, delete_record, describe_table, get_record, get_record_history, list_tables, navigate, present_results, query_records, render_widget, save_analysis, search_help, search_knowledge, search_records, update_record ``` After (28): the same, plus `describe_process`, `get_run_status`, `list_processes`, `run_process`, each in its sorted place. Every line that was there is byte for byte what it was, except that the descriptions of `create_record`, `update_record` and `delete_record` no longer end "unless the administrator turned confirmation off". For a person who may run no process (10 tools, before and after, byte for byte): ``` count_records, describe_table, get_record, list_tables, navigate, present_results, query_records, render_widget, search_help, search_records ``` The process tools appear only for a person who may run at least one process through the assistant, and not when the policy leaves them out. The host of that test registered no reviewed list, so the person who may change tables is offered the four (the tables' bulk actions), and the person of the scenario `everythingAsAnotherPerson`, who may run processes of another QBit and change no table, is not: that scenario's lines in the stored file are without the four process tools (the one expectation of `ChatToolSetTest` that changed after the review). ## Where the code differs from the design Recorded in `docs/actions-and-mcp.md`, section 4.5. In short: a process the person may not run is refused as if it did not exist (not with the operation's "permission denied"); two more kinds of process are never runnable (no screen offers it; its table is read-only for the assistant); a download is not drivable (only links into the application are emitted); the second card at a review is proposed with the run's reference instead of being created by the confirmation; a run over records is refused at confirmation when one of its records is gone; possible values of an input are not listed. After the review: the reviewed list is enforced when the application runs, not only in its build; a field whose name a process keeps for itself is not an input; a run stops at every later screen that has something to show; the records of a run are fixed at the proposal; a card to go on carries the run's version; a process that may run as a job is never run another way; text of the application reaches a model only inside the data envelope. ## Also - A scheduled task is offered `run_process` only with its write approval. - UI API extension `assistant` 1.10.0, additive: `AssistantActionV1.processName`, `processLabel`; `changes` carries the card's lines for a `process_run`. A released panel draws the card as a record change (it knows only the three record tools); the run works. - `AssistantStore` is still the only code that calls data actions. No schedule or report tool (that is A5). ## Not closed - The panel's process card and "start the job in the same click" are the panel's package. Until then a durable job waits for the person on its job record. - The possible values of a process input are not listed to the model. - A run that stopped or is still working is kept in the memory of the server that started it (as core keeps the run): another replica, or a restart, does not know it. ## Changes after the independent review The review's verdict was "fix first". Each change is described by the guarantee it gives, with the test that shows it. | | Guarantee | Test | |---|---|---| | 1 | **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: the inputs the process's backend steps declare, and a fixed set this QBit owns (`ProcessControlNames.RESERVED`). `describe_process` lists such a field under `cannotBeSetThroughTheAssistant`, a value for it is refused before a card exists and again at confirmation, and the process stays runnable for its other fields. | `ProcessRequestsTest.aFieldWhoseNameTheProcessKeepsForItselfIsNeverSent` (a fixture table with an editable field named `sourceTable`: every request of the run is read, and none carries a value under that name), `ProcessControlNamesTest` | | 2 | **A person confirms what they were shown.** A run stops at every later screen that has something to show: a form (also with no required field), values to read, a text, a list of records. Going on is a second card that shows what the screen shows, as the server read it. Only a screen with nothing to show is passed; the screen a run starts at is on the first card. | `ProcessToolsTest.aRunStopsAtALaterScreenWithATextUntilACardShowedIt`, `aRunStopsAtALaterFormWhoseValuesAStepComputed`, `aRunStopsAtALaterScreenWithValuesToRead`, `aRunStopsAtALaterScreenThatListsRecords`, `aScreenWithNothingToShowIsPassedAndTheFirstScreenIsOnTheFirstCard` | | 3 | **Only a reviewed process runs, decided when the application runs.** See "The reviewed list" above. | `ProcessToolsTest.onlyAReviewedProcessOrATablesOwnBulkActionRuns` (no list, an empty list, a list that also names processes a rule keeps away, an administrator's row that adds nothing, a card made before the list was taken away), `theReviewHelperFailsWhenTheListAndTheApplicationDrift` | | 4a | **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, never the filter. | `ProcessToolsTest.aRunIsOverExactlyTheRecordsItsCardCounted`, `aFilterThatFindsMoreThanTheCapIsRefused`, `ProcessRequestsTest.aRunProposedWithAFilterIsSentWithTheRecordsOfItsCard` | | 4b | **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, nothing is proposed and nothing runs on a confirmation. | `ProcessRequestsTest.aProcessThatMayRunAsAJobIsNeverRunOnConfirmationInstead` | | 4c | **Text of the application reaches a model only as data**, inside the 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. A person reads the same words as they are. | `ProcessToolsTest.whatTheApplicationSaidReachesAModelOnlyAsData` (a conversation: propose, confirm, ask again; the next request is read whole) | | 4d | **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; an older card of the same run is refused. | `ProcessToolsTest.aCardGoesOnOnlyWithTheRunAsItStoodWhenTheCardWasMade` | **The reserved names, and where they come from.** `ProcessControlNames.RESERVED` was read from the QQQ version this repository builds against (4.1.0-RC.32): every name `StreamedETLWithFrontendProcess`, `StreamedETLProcess`, the streamed preview, validate and execute steps, `ExtractViaQueryStep`, the `LoadVia...` steps and the bulk edit and bulk delete steps read from or write to a run's values; every name `RunProcessAction` and `RunBackendStepAction` read from them; and the names the process operations carry a run's records, identity and timing in. `ProcessControlNamesTest` reflects over the constants those classes declare and over the inputs a streamed process's backend steps declare, and fails when the set lacks one, so a later QQQ that adds a name is noticed. The inputs of a process's own backend steps are added per process, from its metadata. **What changes for a person or a host, beyond the above.** - A process of a host's own that declares a screen's field as an input of a backend step as well: that field is set in the application, not through the assistant. If it is required and has no default, the process is not run through the assistant, and the person is told where to run it. - A screen that lists records shows the first ten on the card and says how many more there are; a column that is a field the assistant may not see the values of is left out. Where the records cannot be shown (the application does not offer the operation that reads them, or they are of a table the assistant may not read), the run does not go on through the assistant. - A text part of a stored message may carry `source` (the tool whose outcome it repeats). A panel shows it as any text. **Kept true:** `ChatToolSetTest` passes with one changed expectation (named above); `ToolCallChecksTest` passes unchanged; `AssistantStore` is still the only code that calls data actions; no new tool. ## Verification `./mvnw --batch-mode --no-transfer-progress -s <settings> -T1C clean verify` at `a9ebe64`: BUILD SUCCESS: the enforcer rules, the tests of all seven modules (600 in `qbit-assistant`, none failed or skipped) and the JaCoCo gate of each. That command runs no Checkstyle, SpotBugs or PMD in this repository. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 1 commit 2026-11-12 21:04:28 +00:00
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>
james.maes added 1 commit 2026-11-12 23:51:33 +00:00
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>
All checks were successful
maven / build (push) Successful in 7m48s
build / maven (push) Successful in 7m48s
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/process-tools:feat/process-tools
git checkout feat/process-tools
Sign in to join this conversation.