Closes#3. Owner requirement (James, 2026-10-12): the workbench's notebooks, shares/library and audit must be standard QQQ tables in the app; the notebook UI can stay custom.
What changes
Six standard tables in the sqlWorkbench app (notebook, cell, share, pinned result, version, execution audit): nav children (no processes in the nav), record views with child lists, edit, export, audits, developer mode. Nothing is hidden any more.
Record security locks (core-enforced, so table UI, /qqq/v1 table API, export, PVS lookups, processes and the workbench endpoints alike):
notebook = owner (sqlWorkbenchUser on ownerUserId) OR a share audience (read-only sqlWorkbenchAudience on share.audienceKey through share->notebook);
cells / versions / pinned results lock through their notebook (one and two joins);
share rows: notebook owner or the named audience; executions: their userId;
admins (sqlWorkbenchAdmin.hasAccess) get sqlWorkbenchAllAccess.
SqlWorkbenchSecurity.applyToSession(qInstance, session, groups) puts the keys in a session (none without workbench access).
New sqlWorkbenchNotebookShare (USER / GROUP / ALL = library). Publishing writes an ALL share; visibility follows the shares. Record actions: Share Notebook, Duplicate Notebook, Restore Version.
Per-table permissions (READ_INSERT_EDIT_DELETE), listed per role: getUserPermissionNames(), getSharePermissionNames(), getAdminPermissionNames().
Customizers keep the notebook invariants identically for the notebook UI and the tables: owner from the session, cell validation/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 (no system-user bypass); its explicit owner/share checks stay as defense in depth. The review fixes (single-use executionUuid / 409, executable-comment classifier, CSV injection guard, ...) are untouched.
DDL: share table + idempotent backfill of library shares (docs/ddl/postgres.sql). README (permissions, administering via tables, adopting/upgrading) and DESIGN (ER diagram, security model) updated.
Depends on QQQ
QRun-IO/qqq#1074 (#1073): partial updates of child rows were always denied by joined write locks (e.g. editing only a cell's content via the v1 API). This PR builds against QQQ 4.1.0-RC.3 for now; it moves to 4.1.0-RC.4 once that candidate is published, and stays draft until then (the matrix test exercises those partial updates).
Tests
TableSecurityMatrixTest: owner / other user / user-share / group-share / library / other sharer / admin / outsider x all six tables x v1 query, count, get, export, insert, update, delete, core actions, PVS lookups, record actions, notebook endpoints; append-only enforcement even for admins with ad hoc permissions; mixed batches; re-targeting; planting ownership; invariants through the tables.
PostgreSQL (Testcontainers): lock queries with DISTINCT de-dup over TEXT columns.
Locally with the QQQ fix: 108 unit + 7 integration tests green, 93% instruction coverage. An independent security review found no high-severity issue; its medium/low findings are fixed in the second commit.
Coordination: the audit agent's docs PR #2 is already in develop; this PR edits the same README sections (Adopting in another app) to match RC.2.
Closes #3. Owner requirement (James, 2026-10-12): the workbench's notebooks, shares/library and audit must be standard QQQ tables in the app; the notebook UI can stay custom.
## What changes
- **Six standard tables** in the `sqlWorkbench` app (notebook, cell, share, pinned result, version, execution audit): nav children (no processes in the nav), record views with child lists, edit, export, audits, developer mode. Nothing is hidden any more.
- **Record security locks** (core-enforced, so table UI, `/qqq/v1` table API, export, PVS lookups, processes and the workbench endpoints alike):
- notebook = owner (`sqlWorkbenchUser` on `ownerUserId`) OR a share audience (read-only `sqlWorkbenchAudience` on `share.audienceKey` through share->notebook);
- cells / versions / pinned results lock through their notebook (one and two joins);
- share rows: notebook owner or the named audience; executions: their `userId`;
- admins (`sqlWorkbenchAdmin.hasAccess`) get `sqlWorkbenchAllAccess`.
- `SqlWorkbenchSecurity.applyToSession(qInstance, session, groups)` puts the keys in a session (none without workbench access).
- **New `sqlWorkbenchNotebookShare`** (USER / GROUP / ALL = library). Publishing writes an ALL share; `visibility` follows the shares. Record actions: Share Notebook, Duplicate Notebook, Restore Version.
- **Per-table permissions** (READ_INSERT_EDIT_DELETE), listed per role: `getUserPermissionNames()`, `getSharePermissionNames()`, `getAdminPermissionNames()`.
- **Customizers** keep the notebook invariants identically for the notebook UI and the tables: owner from the session, cell validation/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 (no system-user bypass); its explicit owner/share checks stay as defense in depth. The review fixes (single-use executionUuid / 409, executable-comment classifier, CSV injection guard, ...) are untouched.
- DDL: share table + idempotent backfill of library shares (`docs/ddl/postgres.sql`). README (permissions, administering via tables, adopting/upgrading) and DESIGN (ER diagram, security model) updated.
## Depends on QQQ
QRun-IO/qqq#1074 (#1073): partial updates of child rows were always denied by joined write locks (e.g. editing only a cell's content via the v1 API). This PR builds against QQQ 4.1.0-RC.3 for now; it moves to 4.1.0-RC.4 once that candidate is published, and stays draft until then (the matrix test exercises those partial updates).
## Tests
- `TableSecurityMatrixTest`: owner / other user / user-share / group-share / library / other sharer / admin / outsider x all six tables x v1 query, count, get, export, insert, update, delete, core actions, PVS lookups, record actions, notebook endpoints; append-only enforcement even for admins with ad hoc permissions; mixed batches; re-targeting; planting ownership; invariants through the tables.
- PostgreSQL (Testcontainers): lock queries with DISTINCT de-dup over TEXT columns.
- Locally with the QQQ fix: 108 unit + 7 integration tests green, 93% instruction coverage. An independent security review found no high-severity issue; its medium/low findings are fixed in the second commit.
Coordination: the audit agent's docs PR #2 is already in develop; this PR edits the same README sections (Adopting in another app) to match RC.2.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Closes #3. Owner requirement (James, 2026-10-12): the workbench's notebooks, shares/library and audit must be standard QQQ tables in the app; the notebook UI can stay custom.
What changes
sqlWorkbenchapp (notebook, cell, share, pinned result, version, execution audit): nav children (no processes in the nav), record views with child lists, edit, export, audits, developer mode. Nothing is hidden any more./qqq/v1table API, export, PVS lookups, processes and the workbench endpoints alike):sqlWorkbenchUseronownerUserId) OR a share audience (read-onlysqlWorkbenchAudienceonshare.audienceKeythrough share->notebook);userId;sqlWorkbenchAdmin.hasAccess) getsqlWorkbenchAllAccess.SqlWorkbenchSecurity.applyToSession(qInstance, session, groups)puts the keys in a session (none without workbench access).sqlWorkbenchNotebookShare(USER / GROUP / ALL = library). Publishing writes an ALL share;visibilityfollows the shares. Record actions: Share Notebook, Duplicate Notebook, Restore Version.getUserPermissionNames(),getSharePermissionNames(),getAdminPermissionNames().NotebookServiceand history run as the caller through the same actions (no system-user bypass); its explicit owner/share checks stay as defense in depth. The review fixes (single-use executionUuid / 409, executable-comment classifier, CSV injection guard, ...) are untouched.docs/ddl/postgres.sql). README (permissions, administering via tables, adopting/upgrading) and DESIGN (ER diagram, security model) updated.Depends on QQQ
QRun-IO/qqq#1074 (#1073): partial updates of child rows were always denied by joined write locks (e.g. editing only a cell's content via the v1 API). This PR builds against QQQ 4.1.0-RC.3 for now; it moves to 4.1.0-RC.4 once that candidate is published, and stays draft until then (the matrix test exercises those partial updates).
Tests
TableSecurityMatrixTest: owner / other user / user-share / group-share / library / other sharer / admin / outsider x all six tables x v1 query, count, get, export, insert, update, delete, core actions, PVS lookups, record actions, notebook endpoints; append-only enforcement even for admins with ad hoc permissions; mixed batches; re-targeting; planting ownership; invariants through the tables.Coordination: the audit agent's docs PR #2 is already in develop; this PR edits the same README sections (Adopting in another app) to match RC.2.
🤖 Generated with Claude Code