feat: operators read print jobs; retire the Remove actions; plain refusal messages #41

Merged
james.maes merged 2 commits from feat/operators-read-jobs-and-plain-messages into main 2026-11-13 15:12:47 +00:00
Owner

Change

Three decided changes for v1.2.0-RC.5. No wire, contract or schema change; print clients need no update. The published OpenAPI document changes in description text only.

1. A holder of printing_operator reads print jobs, read only

PrintingReadPermission let an operator read print stations and printers and required printing_admin for print jobs, so a person who prints could not see what was printed or ask what was printed today.

  • An operator now has every read shape on printing_job that an administrator has: query, get, count, aggregate, export.
  • What an operator sees: every print job of their tenant, whoever submitted it, as for print stations and printers. The tenant lock on the table (printing_tenant) is the boundary. A host that wants a narrower view adds its own record lock.
  • Fields: the ones an administrator reads. The table has no field for the document (content_base64), its storage reference (content_storage_table, content_reference, content_binding), the retained binding (retained_binding_json), the retained actor or the accepted event, and no token or credential. Two fields a reviewer may want to look at, both left visible:
    • idempotency_key. For an operator request it is operator:<SHA-256 of the submitting person's user reference>:<request id>; for a retained request it is retained:<request id>. It is not a credential: the operator routes derive the key from the caller's own identity, so another person cannot reconcile or replay the request, and a retained observation also needs the accepted event id (not on the table) and the host's own authorization. It does let an operator see which jobs came from the same person.
    • content_hash, the SHA-256 of the document. It shows that two jobs printed the same document, nothing of the content.
  • Unchanged: control history (printing_device_command) and deletion history (printing_deletion) need printing_admin. Nobody gains a table write. Every action on a job, a printer or a station needs printing_admin as before. The HTTP routes are unchanged. The Printing app entry in the navigation stays an administrator's.
  • This reverses a property two tests asserted (PrintingOperatorTest and RetainedPrintFacadeTest: an operator cannot enumerate jobs). Both now assert the new rule and that control history stays refused.

2. The Remove actions are retired; only Delete remains

Removed from the screens and the metadata:

  • process Remove print station (printingRemoveDevices) and its control action PrintingDeviceProcesses.Action.REMOVE;
  • process Remove printers (printingRemoveSelectedPrinters).

What stays:

  • Every HTTP and agent route. No route called a Remove process. The print client calls no remove, unshare or revoke route (it calls session, device/bind, devices/enroll, device/printers, device/heartbeat, device/jobs/claim, device/jobs/queued, job content, events, retry, cancel, device/test-print, device/control/poll, the command receipt, operator/printers and the realtime socket). POST .../admin/devices/{deviceId}/actions with REVOKE still requires and runs printingRevokeDevices.
  • Revoke print station access, Delete print stations, Delete printers.
  • PrintingService.removePrinter and removeDevice, as service methods for a host that calls them. Nothing in the library calls them now.
  • Records archived earlier (removed_at): hidden from the catalog, claims, the PRINTERS frame and the selectors; a queue shared again comes back awaiting approval; retention cleanup still purges archived records no job references; an archived record can be deleted.

3. Refusals say what happened and what to do, and name no framework

Statuses, error codes and JSON field names are unchanged. PrintingQBitConfig.withProductName(name) (optional) puts the host's display name where the sentences say "this application".

Before After Where it can be returned
Printing administrator permission is required You are signed in to this application, but your account is not allowed to manage print stations. Ask an administrator to give you the printing administrator role. Screens, table and process API. Not the printing HTTP routes (they answer the 403 sentence below).
Printing operator permission is required You are signed in to this application, but your account is not allowed to use printing. Ask an administrator to give you the printing operator role. Same.
Permission to delete print stations and printers is required You are signed in to this application, but your account is not allowed to delete print stations and printers. Ask an administrator to give you the permission to delete print stations and printers, in addition to the printing administrator role. Same.
The current session cannot manage this printing tenant You are signed in to this application, but your account is not set up for printing in this organization. Ask an administrator to give your account printing access for your organization. Same.
(the three checks, with no session) Sign in to this application to manage print stations. / to use printing. / to delete print stations and printers. Same.
Sign in with an authorized QQQ user before controlling a print station Sign in to this application to control a print station. Control actions on the screens. No printing HTTP route.
User or device is not authorized You are signed in, but your account or this print station is not allowed to do this. Ask an administrator to check that you have the printing administrator role and that this print station still has access. Every 403 of the 17 station and administration routes under /qqq/v1/qbit/printing/, which the print client calls. Fields error and message; status 403 and code PERMISSION_DENIED unchanged. The print client does not read the body.
User or device is not authorized You are signed in, but your account is not allowed to print here. Ask an administrator to check that you have the printing operator role and that the printer is approved. Every 403 of the 3 operator routes (operator/printers, operator/jobs, operator/requests/{requestId}). Same fields, status and code.
Enrollment requires native QQQ authentication This server enrolls a print station only for a signed-in person. Sign in from the print client, then enroll again. Legacy POST /printing/v1/enroll, 403, code PRINTING_403 unchanged, field message.
Printing administrator permission required Your account is not allowed to manage print stations. Ask an administrator to give you the printing administrator role. Legacy /printing/v1/admin/... routes, 403, code PRINTING_403 unchanged.
A standard QQQ session and the printing subprotocol are required A signed-in session cookie and the printing subprotocol are required No route: the realtime upgrade answers "Realtime authentication or origin denied" for every refusal.
Requires the current QQQ user session, ... / Native QQQ sign-in required Requires a signed-in user session, ... / Sign-in required OpenAPI descriptions only.

One sentence per route family still covers every reason for a 403, so the answer does not say which check failed.

What a host changes when it pins this version

  • Permissions. Nothing to grant. Everyone who holds printing_operator can read the tenant's print jobs after the upgrade. A host for which that is too wide adds a record lock to printing_job. Role documents that say an operator cannot read job history need the new sentence.
  • Removed processes. Delete printingRemoveDevices and printingRemoveSelectedPrinters from help entries, reviewed or allow-listed process lists, role and permission documents, saved links and tests that enumerate the Printing processes.
  • Message texts. A test or help page that quotes an old sentence needs the new one. Code that reads the status or the code needs no change.
  • Optional. withProductName("...").

Validation

  • Maven wrapper verify (Checkstyle and tests): ./mvnw -B -Pui-demo -s <settings adding only the public registry> clean verify, nothing skipped: BUILD SUCCESS on head 1d23c98; 235 tests, 0 failures, 0 errors, 1 skipped (PrintingWorkloadTest, which runs only when its workload property is set and is untouched here).
  • Shared contract compatibility considered: PrintingUiApiContractTest passes against the regenerated printing.yaml and the frozen printing-1.0.0.yaml baseline; the diff of printing.yaml is description lines only. Extension version stays 2.4.0.
  • Documentation/changelog updated: CHANGELOG (1.2.0-RC.5 section with the upgrade notes), README, SECURITY, UI walkthrough, storage and retention, operator catalog, retained print facade.

Tests that prove each change:

  • Operators read jobs: PrintingOperatorReadTest (five tests: reads, counts and groups the tenant's jobs and never another tenant's; the job row has no document, binding or credential field, and a new field fails the test until reviewed; control and deletion history refused; no write and no administrator action; a person with neither permission refused everything).
  • Remove retired: PrintingDeviceProcessesTest.removeActionsAreRetiredAndTheServiceArchiveKeepsHistory, PrintingProcessesTest.removedPrintersAreArchivedUntilTheDeviceSharesThemAgain, PrintingExtractTest (16 selection processes, was 18).
  • Messages: PrintingMessagesTest; PrintingUiApiTest asserts over real HTTP that a refused native request keeps status 403, code PERMISSION_DENIED and the fields error / message, and that a refused legacy enrollment keeps PRINTING_403.

Not run: nothing was printed; no PostgreSQL (the suite uses the in-memory database, as before); the UI was not opened in a browser.

🤖 Generated with Claude Code

## Change Three decided changes for `v1.2.0-RC.5`. No wire, contract or schema change; print clients need no update. The published OpenAPI document changes in description text only. ### 1. A holder of `printing_operator` reads print jobs, read only `PrintingReadPermission` let an operator read print stations and printers and required `printing_admin` for print jobs, so a person who prints could not see what was printed or ask what was printed today. - An operator now has every read shape on `printing_job` that an administrator has: query, get, count, aggregate, export. - **What an operator sees: every print job of their tenant**, whoever submitted it, as for print stations and printers. The tenant lock on the table (`printing_tenant`) is the boundary. A host that wants a narrower view adds its own record lock. - **Fields: the ones an administrator reads.** The table has no field for the document (`content_base64`), its storage reference (`content_storage_table`, `content_reference`, `content_binding`), the retained binding (`retained_binding_json`), the retained actor or the accepted event, and no token or credential. Two fields a reviewer may want to look at, both left visible: - `idempotency_key`. For an operator request it is `operator:<SHA-256 of the submitting person's user reference>:<request id>`; for a retained request it is `retained:<request id>`. It is not a credential: the operator routes derive the key from the caller's own identity, so another person cannot reconcile or replay the request, and a retained observation also needs the accepted event id (not on the table) and the host's own authorization. It does let an operator see which jobs came from the same person. - `content_hash`, the SHA-256 of the document. It shows that two jobs printed the same document, nothing of the content. - Unchanged: control history (`printing_device_command`) and deletion history (`printing_deletion`) need `printing_admin`. Nobody gains a table write. Every action on a job, a printer or a station needs `printing_admin` as before. The HTTP routes are unchanged. The Printing app entry in the navigation stays an administrator's. - **This reverses a property two tests asserted** (`PrintingOperatorTest` and `RetainedPrintFacadeTest`: an operator cannot enumerate jobs). Both now assert the new rule and that control history stays refused. ### 2. The Remove actions are retired; only Delete remains Removed from the screens and the metadata: - process **Remove print station** (`printingRemoveDevices`) and its control action `PrintingDeviceProcesses.Action.REMOVE`; - process **Remove printers** (`printingRemoveSelectedPrinters`). What stays: - Every HTTP and agent route. No route called a Remove process. The print client calls no remove, unshare or revoke route (it calls `session`, `device/bind`, `devices/enroll`, `device/printers`, `device/heartbeat`, `device/jobs/claim`, `device/jobs/queued`, job `content`, `events`, `retry`, `cancel`, `device/test-print`, `device/control/poll`, the command `receipt`, `operator/printers` and the realtime socket). `POST .../admin/devices/{deviceId}/actions` with `REVOKE` still requires and runs `printingRevokeDevices`. - **Revoke print station access**, **Delete print stations**, **Delete printers**. - `PrintingService.removePrinter` and `removeDevice`, as service methods for a host that calls them. Nothing in the library calls them now. - Records archived earlier (`removed_at`): hidden from the catalog, claims, the `PRINTERS` frame and the selectors; a queue shared again comes back awaiting approval; retention cleanup still purges archived records no job references; an archived record can be deleted. ### 3. Refusals say what happened and what to do, and name no framework Statuses, error codes and JSON field names are unchanged. `PrintingQBitConfig.withProductName(name)` (optional) puts the host's display name where the sentences say "this application". | Before | After | Where it can be returned | | --- | --- | --- | | Printing administrator permission is required | You are signed in to this application, but your account is not allowed to manage print stations. Ask an administrator to give you the printing administrator role. | Screens, table and process API. Not the printing HTTP routes (they answer the 403 sentence below). | | Printing operator permission is required | You are signed in to this application, but your account is not allowed to use printing. Ask an administrator to give you the printing operator role. | Same. | | Permission to delete print stations and printers is required | You are signed in to this application, but your account is not allowed to delete print stations and printers. Ask an administrator to give you the permission to delete print stations and printers, in addition to the printing administrator role. | Same. | | The current session cannot manage this printing tenant | You are signed in to this application, but your account is not set up for printing in this organization. Ask an administrator to give your account printing access for your organization. | Same. | | (the three checks, with no session) | Sign in to this application to manage print stations. / to use printing. / to delete print stations and printers. | Same. | | Sign in with an authorized QQQ user before controlling a print station | Sign in to this application to control a print station. | Control actions on the screens. No printing HTTP route. | | User or device is not authorized | You are signed in, but your account or this print station is not allowed to do this. Ask an administrator to check that you have the printing administrator role and that this print station still has access. | Every `403` of the 17 station and administration routes under `/qqq/v1/qbit/printing/`, which the print client calls. Fields `error` and `message`; status `403` and code `PERMISSION_DENIED` unchanged. The print client does not read the body. | | User or device is not authorized | You are signed in, but your account is not allowed to print here. Ask an administrator to check that you have the printing operator role and that the printer is approved. | Every `403` of the 3 operator routes (`operator/printers`, `operator/jobs`, `operator/requests/{requestId}`). Same fields, status and code. | | Enrollment requires native QQQ authentication | This server enrolls a print station only for a signed-in person. Sign in from the print client, then enroll again. | Legacy `POST /printing/v1/enroll`, `403`, code `PRINTING_403` unchanged, field `message`. | | Printing administrator permission required | Your account is not allowed to manage print stations. Ask an administrator to give you the printing administrator role. | Legacy `/printing/v1/admin/...` routes, `403`, code `PRINTING_403` unchanged. | | A standard QQQ session and the printing subprotocol are required | A signed-in session cookie and the printing subprotocol are required | No route: the realtime upgrade answers "Realtime authentication or origin denied" for every refusal. | | Requires the current QQQ user session, ... / Native QQQ sign-in required | Requires a signed-in user session, ... / Sign-in required | OpenAPI descriptions only. | One sentence per route family still covers every reason for a `403`, so the answer does not say which check failed. ### What a host changes when it pins this version - **Permissions.** Nothing to grant. Everyone who holds `printing_operator` can read the tenant's print jobs after the upgrade. A host for which that is too wide adds a record lock to `printing_job`. Role documents that say an operator cannot read job history need the new sentence. - **Removed processes.** Delete `printingRemoveDevices` and `printingRemoveSelectedPrinters` from help entries, reviewed or allow-listed process lists, role and permission documents, saved links and tests that enumerate the Printing processes. - **Message texts.** A test or help page that quotes an old sentence needs the new one. Code that reads the status or the code needs no change. - **Optional.** `withProductName("...")`. ## Validation - [x] Maven wrapper verify (Checkstyle and tests): `./mvnw -B -Pui-demo -s <settings adding only the public registry> clean verify`, nothing skipped: BUILD SUCCESS on head `1d23c98`; 235 tests, 0 failures, 0 errors, 1 skipped (`PrintingWorkloadTest`, which runs only when its workload property is set and is untouched here). - [x] Shared contract compatibility considered: `PrintingUiApiContractTest` passes against the regenerated `printing.yaml` and the frozen `printing-1.0.0.yaml` baseline; the diff of `printing.yaml` is description lines only. Extension version stays 2.4.0. - [x] Documentation/changelog updated: CHANGELOG (1.2.0-RC.5 section with the upgrade notes), README, SECURITY, UI walkthrough, storage and retention, operator catalog, retained print facade. Tests that prove each change: - Operators read jobs: `PrintingOperatorReadTest` (five tests: reads, counts and groups the tenant's jobs and never another tenant's; the job row has no document, binding or credential field, and a new field fails the test until reviewed; control and deletion history refused; no write and no administrator action; a person with neither permission refused everything). - Remove retired: `PrintingDeviceProcessesTest.removeActionsAreRetiredAndTheServiceArchiveKeepsHistory`, `PrintingProcessesTest.removedPrintersAreArchivedUntilTheDeviceSharesThemAgain`, `PrintingExtractTest` (16 selection processes, was 18). - Messages: `PrintingMessagesTest`; `PrintingUiApiTest` asserts over real HTTP that a refused native request keeps status `403`, code `PERMISSION_DENIED` and the fields `error` / `message`, and that a refused legacy enrollment keeps `PRINTING_403`. Not run: nothing was printed; no PostgreSQL (the suite uses the in-memory database, as before); the UI was not opened in a browser. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
james.maes added 2 commits 2026-11-13 14:21:00 +00:00
Operators read print jobs, read only. PrintingReadPermission accepted
printing_operator for print stations and printers and required
printing_admin for print jobs, so a person who prints could not see what
was printed. A holder of printing_operator now has every read shape on
printing_job that an administrator has (query, get, count, aggregate,
export) and sees every job of the tenant, with the same fields; the table
has no field for the document, its storage reference or the retained
binding. Control history and deletion history stay administrator-only and
nobody gains a write.

Retire the Remove actions. Delete (RC.3) keeps job history and records
who deleted what, so the older archive actions duplicated it on the
screens. Removed: the processes printingRemoveDevices and
printingRemoveSelectedPrinters and the control action Action.REMOVE. No
HTTP or agent route called them; the REVOKE control still resolves to
printingRevokeDevices. PrintingService.removePrinter and removeDevice
stay as service methods, and records archived earlier behave as before.

Refusals say what happened and what to do, and name no framework. The
permission checks, the 403 text of the native and legacy routes, the
control-action sign-in text and the OpenAPI descriptions are reworded;
statuses, error codes and JSON field names are unchanged. A host may give
its application's display name with PrintingQBitConfig.withProductName;
the default is "this application".

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
test: the selection-process count follows the two retired Remove processes
maven / build (push) Successful in 3m44s
build / maven (push) Successful in 3m44s
1d23c985fd
PrintingExtractTest counts the streamed-selection processes that use the
enforced extract step: 18 before, 16 without printingRemoveDevices and
printingRemoveSelectedPrinters.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
james.maes merged commit 2bb4e580b8 into main 2026-11-13 15:12:47 +00:00
Sign in to join this conversation.