Files
james.maesandClaude Fable 5.2 d99db4738f
maven / build (push) Successful in 1m17s
build / maven (push) Successful in 1m17s
fix: a rule read that asks for its own rows no longer recurses
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>
2026-11-14 01:56:42 -06:00

6.4 KiB

Changelog

All notable changes are documented here, following Keep a Changelog and Semantic Versioning.

[Unreleased]

Fixed: rule reads that ask for their own rows

  • Reading the rule rows a node keeps (classifications, active retention rules, policies) no longer recurses when code that runs inside that read asks for the same rows. Another QBit's virtual field value provider, or a post-query customizer, on a secured-data table that ran a query with filter criteria did exactly that: with one classification row present, every request that needed a classification ended in a StackOverflowError.
  • The QBit's system reads of its own tables (SystemWrites.query, the audit key lookup) 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 that read began (none before the first read) and starts no other read. Only the reading thread, inside the QBit's system read, can be given that answer; every request gets the rows the read returns. The same holds for a transaction's read of its own retention rule edits.
  • A rule read that fails is tried again by the next question instead of failing or recursing from then on.

Fixed: access-log lifecycle

  • The access-log worker and JVM shutdown hook hold the logger weakly, so an instance the host drops (tests, reloads) can be collected; its worker stops at the next tick.
  • withRegisterShutdownHook(false) for hosts that call SecuredDataRuntime.close() themselves; SecuredDataAccessLogger.isWorkerRunning().
  • Virtual (computed) fields resolve through the table classification instead of throwing (#18).
  • Idle access-log ticks open no context, so the worker never validates an unfinished host instance, and a failed tick no longer stops the worker (#18).

Changed: UI API 1.6.0

  • UI API extension secured-data 1.1.0 (requires QQQ 4.1.0-RC.15, UI API 1.6.0): operationIds, standard error responses with BasicErrorResponseV1, extension tags, and retained SecuredDataErrorV1 schema deprecated since 1.1.0.

Changed — direct core field access and audit protection

  • Remove all #1172/#1182 stand-ins and reflective bridges; use core field-access, classified-value token, audit protection, wipe flag and shredded-event APIs directly.
  • Register the resolver, key source and audit protector through core supplemental metadata.
  • Run core field-access integration and retention proof tests unconditionally.
  • Pin QQQ to 4.1.0-RC.7, which ships field access (#1172) and audit value protection (#1182); all core stand-ins and reflective bridges are removed.

Changed — RC.6 migration

  • Pin QQQ to 4.1.0-RC.6; use released classification, client-channel and table-feature APIs directly.
  • Fold UI API code and tests into the main build and remove the profile and reflective registration.
  • Remove released-API stand-ins and the local contract assertion facade; retain #1172 field-access stand-ins with RC.7 removal markers.

Added: phase (f) retention and wipe

  • Retention rules, wipe markers, legal holds, wipe runs and a hidden, locked audit-key table, with the secured_data.retention permission and a "Retention rules" navigation entry.
  • Actions NULL_WITH_MARKER, OVERWRITE_TEXT, KEYED_HASH, DELETE_RECORD and ARCHIVE_THEN_WIPE (host ArchiveSinkInterface), with validation and low-entropy refusal.
  • Atomic wipe batches (fields, markers, key deletions, audits, access log and run row in one rollback-capable transaction), per-record and per-table legal holds, a Review/Result "Run wipe now" process and a daily scheduled run.
  • Per-record-field AES-GCM wrapped DEKs (AAD-bound) for crypto-shredding of audit tokens, CHANGED_ONLY audit treatment, and the audit-value protector.
  • App-home warning when known backup retention exceeds a rule's age_days; withBackupRetentionDays and withArchiveSink options; README retention section and the backups residual risk.
  • Tests: all actions, holds, rollback, Review/Result, schedule, permissions, wrap/unwrap, and the section 15 canary and wipe-proof tests.

Fixed

  • The key source returned a DEK array it then zeroed; it now returns copies, with a regression test.

Added

  • Phase (e) configurable reveal-median, distinct-record and exact-match anomaly detection, with system access-log counters, bounded daily median caching, per-user/day alerts, and flags retained without an alert sink.

  • Break-glass start and capped-reveal anomaly flags alongside the existing alerts.

  • One Secured Data app home with six live KPIs, six full-width responsive lists, filtered View all links, theme tokens, compact dates, clamped text/tooltips and explicit empty states.

  • Transaction-aware, value-free policy/classification change audit rows from post-write customizers and two access-log indexes for anomaly/home queries.

  • Boundary, alert, home data, rendering and navigation tests plus a synthetic Next demo harness for coordinator screenshot capture.

  • Phase (c) HTTP-independent reveal options, reason validation, UTC daily limits, grants, audit links, cap alerts and assistant refusal.

  • Five-second per-user grant cache with immediate invalidation and read-time expiration.

  • Start/End break-glass table and record actions, streamed Review/Result summaries, owner/admin authorization, scheduled expiration, alerts and optional admin bell notification.

  • Caller status and the versioned secured-data UI API extension behind the temporary uiapi Maven profile, with committed OpenAPI and contract/HTTP tests.

  • Contingent classification field picker support for snake_case and camelCase table selections; unknown-table rejection.

  • Memory and real-core coverage for reveal, break-glass, record locks and expiry. Reason columns support 500 characters; existing RDBMS installations must widen the three reason columns to VARCHAR(500).

  • Phase (b) secured-data classification, policy, reveal-grant, break-glass and insert-only access-log tables, metadata, permissions, pickers and app navigation.

  • Versioned classification and policy resolution with channel caps, table features, break-glass expiry and access fingerprints.

  • Bounded asynchronous value-free logging, HMAC search fingerprints, rate limits, entropy rules and enumeration alerts.

  • RC.5-compatible stand-ins and reflective core adapters; conditional core Query/Get integration coverage.

  • PostgreSQL DDL, H2 persistence/constraint coverage, and configuration/deployment documentation.