Port of #43 to the 0.1 release line: the same commit, cherry-picked onto release/0.1 (the two branches differ only in the version in pom.xml). Earlier fixes reached this line by merging develop into it (#40, #42); this one is a pull request of its own so that it can be reviewed and released without waiting for that merge. Merge #43 first.
The defect
With one row in secured_data_classification, and another QBit's virtual field value provider on that table whose work is a query with filter criteria, every request that needed a classification ended in StackOverflowError. Core asks this QBit for a classification when it validates a query's criteria. The provider's query ran inside the read of the classification rows, so its question started that read again, and nothing was ever recorded that would stop the next request doing the same. The active retention rules and the policies were read in the same way.
The fix
System reads of the QBit's own tables name the stored fields of the table, so core runs no virtual field value provider inside them.
A question asked from inside a rule read is answered from the rows held when the read began (none before the first read) and starts no other read (RuleRows, used for classifications, retention rules and policies). This covers code that naming the fields cannot switch off, such as a post-query customizer.
A read that fails records nothing, so the next question reads again.
A transaction's read of its own retention rule edits is guarded in the same way.
Only the reading thread, inside the QBit's system read, can be given the nested answer. There the session is the system session and the resolver answers FULL without looking at a classification, so no field access decision is made from it. Every request, on that thread or another, gets the rows the read returns, or the error. #43 has the full argument and the table of tests.
Tests
SecuredDataRuleReloadTest, 11 tests; 9 of them end in StackOverflowError without the main-code changes.
Build
./mvnw -B verify on this head: 112 tests, 0 failures, Checkstyle and the JaCoCo gates green. Nothing skipped.
Port of #43 to the 0.1 release line: the same commit, cherry-picked onto `release/0.1` (the two branches differ only in the version in `pom.xml`). Earlier fixes reached this line by merging `develop` into it (#40, #42); this one is a pull request of its own so that it can be reviewed and released without waiting for that merge. Merge #43 first.
## The defect
With one row in `secured_data_classification`, and another QBit's virtual field value provider on that table whose work is a query with filter criteria, every request that needed a classification ended in `StackOverflowError`. Core asks this QBit for a classification when it validates a query's criteria. The provider's query ran inside the read of the classification rows, so its question started that read again, and nothing was ever recorded that would stop the next request doing the same. The active retention rules and the policies were read in the same way.
## The fix
1. System reads of the QBit's own tables name the stored fields of the table, so core runs no virtual field value provider inside them.
2. A question asked from inside a rule read is answered from the rows held when the read began (none before the first read) and starts no other read (`RuleRows`, used for classifications, retention rules and policies). This covers code that naming the fields cannot switch off, such as a post-query customizer.
3. A read that fails records nothing, so the next question reads again.
4. A transaction's read of its own retention rule edits is guarded in the same way.
Only the reading thread, inside the QBit's system read, can be given the nested answer. There the session is the system session and the resolver answers `FULL` without looking at a classification, so no field access decision is made from it. Every request, on that thread or another, gets the rows the read returns, or the error. #43 has the full argument and the table of tests.
## Tests
`SecuredDataRuleReloadTest`, 11 tests; 9 of them end in `StackOverflowError` without the main-code changes.
## Build
`./mvnw -B verify` on this head: 112 tests, 0 failures, Checkstyle and the JaCoCo gates green. Nothing skipped.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reading the rule rows a node keeps (classifications, active retention rules,
policies) is a query on a secured-data table, and other code runs inside that
query: a virtual field value provider or a post-query customizer that another
QBit or the host put on the table. When that code runs a query with filter
criteria, core validates the operands of classified fields and asks this QBit
for the classification of the field, which needs the rows that are being
read. The version of the rows was recorded only after the read returned, so
the nested question started the read again, and that read asked again:
StackOverflowError, and the same on every later request, since nothing was
ever recorded. One classification row was enough (core runs no value provider
for an empty result).
- System reads of the QBit's own tables (SystemWrites.query, the audit key
lookup) name the stored fields of the table. Core fills a virtual field only
for a query that selects it, so no value provider runs inside a system read.
- RuleRows holds one kind of rule rows and is used for all three kinds, which
had the same shape. A question asked from inside a read is answered from the
rows held when the read began (none before the first read) and never starts
another read. The owner's monitor makes that answer private to the reading
thread: every other thread waits and gets the rows the read returns.
- A read that fails records nothing and clears the reading mark, so the next
question reads again. The rows held before are not given to a request in
place of rows that could not be read.
- RetentionRules.active: a question asked from inside a transaction's read of
its own rule edits is answered from the rules every thread shares instead
of reading the edits again.
Who can be given the nested answer: only code on the reading thread, inside
SystemWrites.call, where the session is the system session and the resolver
answers FULL without looking at a classification. No field access decision
for a person is made from it; the request that started the read, and every
request on another thread, gets the rows the read returns.
Tests (SecuredDataRuleReloadTest): 9 of the 11 end in StackOverflowError
without the fix. They cover a value provider and a post-query customizer on
the classification, retention rule and policy tables, the answer a nested
question gets in both directions of a change, the session it is asked in, a
second thread waiting for the read, and failed reads (an exception and an
Error) being read again.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Port of #43 to the 0.1 release line: the same commit, cherry-picked onto
release/0.1(the two branches differ only in the version inpom.xml). Earlier fixes reached this line by mergingdevelopinto it (#40, #42); this one is a pull request of its own so that it can be reviewed and released without waiting for that merge. Merge #43 first.The defect
With one row in
secured_data_classification, and another QBit's virtual field value provider on that table whose work is a query with filter criteria, every request that needed a classification ended inStackOverflowError. Core asks this QBit for a classification when it validates a query's criteria. The provider's query ran inside the read of the classification rows, so its question started that read again, and nothing was ever recorded that would stop the next request doing the same. The active retention rules and the policies were read in the same way.The fix
RuleRows, used for classifications, retention rules and policies). This covers code that naming the fields cannot switch off, such as a post-query customizer.Only the reading thread, inside the QBit's system read, can be given the nested answer. There the session is the system session and the resolver answers
FULLwithout looking at a classification, so no field access decision is made from it. Every request, on that thread or another, gets the rows the read returns, or the error. #43 has the full argument and the table of tests.Tests
SecuredDataRuleReloadTest, 11 tests; 9 of them end inStackOverflowErrorwithout the main-code changes.Build
./mvnw -B verifyon this head: 112 tests, 0 failures, Checkstyle and the JaCoCo gates green. Nothing skipped.🤖 Generated with Claude Code