fix: resolve virtual fields; keep the access-log worker off unfinished instances #18

Merged
KofTwentyTwo merged 1 commits from feature/virtual-fields-and-idle-flush into develop 2026-10-16 16:35:25 +00:00
KofTwentyTwo commented 2026-10-16 13:42:19 +00:00 (Migrated from github.com)

Found while installing 0.1.0-RC.2 into kof22 (KofTwentyTwo/Website-Backend).

Virtual fields

SecuredDataClassifications.effective(table, fieldName) used QTableMetaData.getField, which throws for a virtual (computed) field. Core resolves every field the table exposes, virtual ones included, so any non-system read of a table with a virtual field failed (Field [sitePath] was not found in table [cms_author]), including the QQQ API middleware's instance validation. Virtual fields now fall back to the table classification (unclassified table: FULL).

Access-log worker

The worker called flush() every interval, and flush() always opened a QContext. QContext.init validates an unvalidated instance, and the worker starts at produce() time while the host is still building the instance, so:

  • the first tick validated and enriched a half-built instance on the worker thread, racing the host's main thread; and
  • when that validation failed, the exception cancelled the scheduled task permanently (scheduleWithFixedDelay suppresses later runs), so no VIEW or exact-match row was ever written.

Idle ticks now return before opening a context, and the scheduled tick catches and logs (value-free) a failed drain so the next tick retries. The qbit's own tests did not see this because they use a 60 s interval.

Tests

  • SecuredDataResolverTest.virtualFieldsTakeTheTableClassification
  • SecuredDataAccessLoggerTest.idleTicksNeverValidateAnUnfinishedInstance

./mvnw verify passes locally (checkstyle, JaCoCo).

Not in this PR (worth a follow-up): the logger's JVM shutdown hook holds every instance strongly (a test suite that builds many instances keeps them all; qbit-assistant#57 solved the same with an opt-out).

🤖 Generated with Claude Code

Found while installing 0.1.0-RC.2 into kof22 (KofTwentyTwo/Website-Backend). ## Virtual fields `SecuredDataClassifications.effective(table, fieldName)` used `QTableMetaData.getField`, which throws for a virtual (computed) field. Core resolves every field the table exposes, virtual ones included, so any non-system read of a table with a virtual field failed (`Field [sitePath] was not found in table [cms_author]`), including the QQQ API middleware's instance validation. Virtual fields now fall back to the table classification (unclassified table: FULL). ## Access-log worker The worker called `flush()` every interval, and `flush()` always opened a `QContext`. `QContext.init` validates an unvalidated instance, and the worker starts at `produce()` time while the host is still building the instance, so: - the first tick validated and enriched a half-built instance on the worker thread, racing the host's main thread; and - when that validation failed, the exception cancelled the scheduled task permanently (`scheduleWithFixedDelay` suppresses later runs), so no VIEW or exact-match row was ever written. Idle ticks now return before opening a context, and the scheduled tick catches and logs (value-free) a failed drain so the next tick retries. The qbit's own tests did not see this because they use a 60 s interval. ## Tests - `SecuredDataResolverTest.virtualFieldsTakeTheTableClassification` - `SecuredDataAccessLoggerTest.idleTicksNeverValidateAnUnfinishedInstance` `./mvnw verify` passes locally (checkstyle, JaCoCo). Not in this PR (worth a follow-up): the logger's JVM shutdown hook holds every instance strongly (a test suite that builds many instances keeps them all; qbit-assistant#57 solved the same with an opt-out). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.