12 Commits
Author SHA1 Message Date
james.maesandClaude Sonnet 5.5 47a3e62a45 fix(audit): prune in bounded background batches, spare RUNNING rows, delay first pass
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 17:16:05 -05:00
james.maesandClaude Opus 5.5 7e9017c376 feat(help): table help, guardrail test and audit retention
Adds help/sql-workbench.yaml (empty-state help for all six tables) applied by
SqlWorkbenchHelp, a tables guardrail test, and the auditRetentionDays config
that DESIGN.md described but did not exist (default null keeps everything).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:58:04 -05:00
james.maesandClaude Opus 5.5 84a7033ae4 feat(api): serve the sql-workbench routes as spec'd UI API extension endpoints
Replace SqlWorkbenchRouteProvider (21 raw /qqq/v1/sql-workbench Javalin
routes) with one MiddlewareSpecExtension "sql-workbench" (prefix
/sql-workbench). JSON operations use AbstractJsonEndpointSpec; the result
export stream extends AbstractEndpointSpec.

Paths, methods and payloads are unchanged, and so are the permission
checks, read-only gates, limits and audit. The generated fragment is
committed at openapi/qqq-v1/sql-workbench.yaml and checked by a contract
test. The server tests run in strict raw-route mode.

The core baseline becomes 4.1.0-RC.6, the first RC with the extension
point (QRun-IO/qqq#1128).

Closes #20 (architecture review QRun-IO/qqq#1150, finding F2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:48:18 -05:00
james.maesandClaude Opus 5.5 51c5a06859 feat!: snake_case QQQ names with the sql_workbench_ prefix (owner naming rule)
Owner rule: table names are lowercase snake_case, prefixed with the
qbit's name. Metadata only; the physical tables were already
sql_workbench_*, so no DDL.

- Tables, notebook PVS: sql_workbench_notebook, _cell, _notebook_version,
  _result_snapshot, _notebook_share, _execution (now equal to their
  backend table names)
- Enum PVS: sql_workbench_share_audience_type
- Permissions: sql_workbench.{hasAccess,write,share},
  sql_workbench_admin.hasAccess, <table>.read etc.
- Security key types: sql_workbench_user (all-access
  sql_workbench_all_access), sql_workbench_audience
- Joins: sql_workbench_notebook_join_sql_workbench_*
- tableNamePrefix / namespace normalized to snake_case, one "_"
- @Deprecated LEGACY_* constants with the pre-RC.3 names
- UPGRADE.md: permissions, security keys, stored references (audit_table,
  saved views/reports) with sample SQL; CHANGELOG 1.0.0-RC.3
- Test for the new names, legacy constants and prefix normalization

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:07:13 -05:00
james.maesandClaude Opus 5.5 ddd85ebe0d feat(#3): expose workbench data as standard QQQ tables secured by record locks
Notebooks, cells, versions, pinned results, shares and the execution
audit are now ordinary QQQ tables in the SQL Workbench app (nav children,
record views with child lists, edit, export, audits, developer mode)
instead of hidden admin-only tables.

- Record security locks (core-enforced, so every entry point): owner OR
  share audience for notebooks and their children (through joins), the
  audit by user; admins hold sqlWorkbenchAllAccess. Key types
  sqlWorkbenchUser / sqlWorkbenchAudience, applied to sessions by
  SqlWorkbenchSecurity.applyToSession (no keys without workbench access).
- New sqlWorkbenchNotebookShare (USER / GROUP / ALL); publishing writes an
  ALL share and visibility follows the shares.
- Per-table read/insert/edit/delete permissions, listed per role by the
  config (user / share / admin).
- Table customizers hold the notebook invariants for the notebook UI and
  the tables alike: owner from the session, cell validation and order,
  one version per change, share keys, append-only versions and audit
  (admins included), pinned results only from pinning, cascades.
- NotebookService and history run as the caller through the same actions;
  explicit owner/share checks stay as defense in depth.
- Record actions: Share Notebook, Duplicate Notebook, Restore Version.
- DDL: share table plus an idempotent backfill of library shares.
- Tests: TableSecurityMatrixTest (role x table x action over the v1 table
  API, core actions, export, PVS, record actions, notebook endpoints) and
  a PostgreSQL lock test. Docs: README, DESIGN, CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:08:10 -05:00
james.maesandClaude Opus 5.5 ec607016bd build: build against the released QQQ 4.1.0-RC.2
Re-pin the default-active qqq-snapshot profile from qqq-bom-pom
4.1.0-RC.1 to 4.1.0-RC.2 (now on Maven Central) and update the docs.
No SNAPSHOT dependencies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:49:10 -05:00
james.maesandClaude Opus 5.5 5342c04a2a docs: warn that production backends need readOnlyCredentials
Without a least-privilege read-only role, read mode runs as the app
role and relies on the classifier (H2 / generic) or a READ ONLY
transaction that still allows side-effecting functions (Postgres).
Also records the security fixes in the CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:40:16 -05:00
james.maesandClaude Opus 5.5 24ce85f187 fix(sql): fail closed on MySQL executable comments and optimizer hints
MySQL runs the contents of /*! ... */ (and MariaDB /*M! ... */) and reads
/*+ ... */ hints, but the scanner skipped them as comments, so
"SELECT * FROM t /*! INTO OUTFILE '/tmp/x' */" classified as READ.

On the MySQL dialect the scanner now emits EXECUTABLE_COMMENT tokens for
these, and the classifier marks any statement containing one FORBIDDEN.
MySQL "--" also only starts a comment when whitespace follows ("1--1"
is arithmetic), so text after it is no longer hidden from the classifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:37:50 -05:00
james.maesandClaude Opus 5.5 52d2d6bc87 fix(engine): make client-chosen executionUuids single-use
Re-sending an in-flight executionUuid overwrote its registration, failed
the audit insert (UNIQUE), and the finally-unregister then removed the
original - escaping maxConcurrentExecutionsPerUser and cancellation.

- RunningStatements.register refuses an id already registered
  (putIfAbsent); unregister(uuid, registration) removes only its own.
- The engine refuses a client id that is running or already in the
  audit, before registering or recording anything
  (DuplicateExecutionException; the route provider answers 409).
- ExecutionAuditor.record() checks the insert result and fails loudly.
- Execution ids are lower-cased at the API edge (execute, rows, export,
  cancel, snapshots) and in engine.cancel.
- Tests for each, including 409 over HTTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:36:09 -05:00
james.maesandClaude Opus 5.5 ebef56f34b fix(security): guard the qbit's tables with a separate admin permission
The five tables used the same HAS_ACCESS_PERMISSION base (sqlWorkbench)
that every workbench user holds, so any user could read and write other
users' personal notebooks, cells, versions, pinned result snapshots and
the full execution audit through the generic /qqq/v1/table API.

- Tables now use adminPermissionBaseName (default sqlWorkbenchAdmin,
  validated to differ from the access base name).
- Cell, version and snapshot tables drop insert/update/delete
  capabilities, like the audit table.
- NotebookService and the history / snapshot audit lookups check the
  caller in code, then read and write as the system user (SystemUser,
  shared with ExecutionAuditor).
- Tests: a second ordinary user (and the owner) get permission denied
  from the table query executor on all five tables; the workbench API
  keeps working for ordinary users; admins can read everything.
- README / DESIGN document the admin permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:33:23 -05:00
james.maesandClaude Opus 5.5 c497e7ece8 feat: accept a client-chosen executionUuid
POST /execute takes an optional executionUuid (validated as a UUID) so
the UI can cancel a running statement before the response arrives.
Generated server-side when absent; the audit's unique key still
guarantees no reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:14:01 -05:00
james.maesandClaude Opus 5.5 13ec09cab3 docs: SQL Workbench qbit design and repository scaffold
Design for a super-user SQL notebook qbit: read-only by default with a
gated, audited write mode over any QQQ RDBMS backend; notebooks with
versions, parameters and snapshots; REST API under
/qqq/v1/sql-workbench; Next UI SQL_WORKBENCH widget. Adds license,
notice, policy files and CODEOWNERS like the sibling qbits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:47:22 -05:00