Files
james.maesandClaude Fable 5.2 d7c7c203c3
maven / build (push) Successful in 2m22s
build / maven (push) Successful in 2m22s
fix(delete): write names only onto jobs that lack one; correct the changelog on the old backfill
Review of PR 40: the name backfill at delete rewrote every job of the station
while holding the tenant's admission lock, and the changelog called the old
startup backfill a no-op where it nulled a queue name.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 17:30:50 -06:00

32 KiB
Raw Permalink Blame History

Changelog

Unreleased (1.2.0)

Print jobs have a real creation date-time. No wire or contract change (extension 2.4.0, wire v1, subprotocol unchanged); connectors need no update. Schema change: a host that owns the printing schema must add one column before deploying this version (below).

  • printing_job.created_at is ISO-8601 text and its QQQ field is a string, so QQQ could not apply a relative date filter ("created in the last day"), a date sort or a date bucket to print jobs. The table gains created_time, a TIMESTAMP without time zone holding UTC (the QQQ date-time convention), written by the same INSERT from the same instant as created_at. It is the read-only QQQ field Created (created_time, DATE_TIME) and replaces the text field in the Print jobs default columns and Summary section.

  • created_at is unchanged: the queue order, history cursors, admission seeding, the Printing home and the createdAt value of the job API still read it. Its QQQ field stays in Details, relabelled Created at (ISO text).

  • initialize() adds the column (ADD COLUMN IF NOT EXISTS), backfills every existing row from created_at on each start (so a row written by an older node during a rolling restart is repaired) and creates printing_job_created_time_idx (tenant_id, created_time, id).

  • A host that owns the schema (it never calls initialize()) appends the equivalent to its own migrations. For PostgreSQL, in one transaction:

    ALTER TABLE printing_job ADD COLUMN IF NOT EXISTS created_time TIMESTAMP WITHOUT TIME ZONE;
    UPDATE printing_job SET created_time = (created_at::timestamptz AT TIME ZONE 'UTC') WHERE created_time IS NULL;
    ALTER TABLE printing_job ALTER COLUMN created_time SET DEFAULT (now() AT TIME ZONE 'UTC');
    ALTER TABLE printing_job ALTER COLUMN created_time SET NOT NULL;
    CREATE INDEX IF NOT EXISTS printing_job_created_time_idx ON printing_job(tenant_id, created_time, id);
    

    The default and NOT NULL are optional hardening for a rolling deployment: a node still on an older library omits the column, and the default gives its rows the database clock in UTC instead of a null that date filters would skip. The library itself needs only the nullable column and always writes it.

  • On a schema without the column this version fails loudly and stores nothing: every submission is rolled back with the database's missing-column error for created_time, and QQQ cannot read the Print jobs table. Apply the schema step first; the previous library keeps working on the new schema.

Delete print stations and printers; "Print station"; grouped reads

No wire or contract change (extension 2.4.0, wire v1, subprotocol unchanged); connectors need no update. Schema change: a host that owns the printing schema applies the step below before deploying this version.

  • Names. The computer that runs the print client is a Print station in every label, action, preview, home page text and process message (it was "Print device", which read like the printer). Printer is unchanged. The words live in PrintingNames; a different choice is one edit there. Table, field and process names (printing_device, printingEnrollDevice, ...), the HTTP API texts and the agent protocol are unchanged. Changed labels a host may have help or tests for: tables Print stations and Print station control history; actions Enroll a print station, Disconnect print stations, Reconnect print stations, Revoke print station access, Remove print station; the enrollment field deviceName is Computer name.

  • Delete print stations (printingDeleteDevices) and Delete printers (printingDeleteSelectedPrinters), on the list selection and on the record screen. Each asks for a confirmation, then shows what will happen (printers that go with a station, queued jobs canceled, jobs closed as outcome unknown, finished jobs kept) and names every record it will not delete, with the reason. Service: PrintingService.deleteDevice(deviceId, actor), deletePrinter(printerRowId, actor), and the read-only deviceDeletionPlan / printerDeletionPlan.

    • A station is revoked through the existing REVOKE control (credential rotated, disconnected, pending commands closed) and, in the same transaction (RC.4, below; it was a separate step before): its queued jobs become FAILED ("Canceled before dispatch: its print station was deleted"); jobs it was sent without a reported result (CLAIMED, RECEIVED, SUBMITTING) become OUTCOME_UNKNOWN; each of those gets a printing_event row (event_origin SERVER_DELETION) naming the actor; its control receipts, its printers and the station row are deleted.
    • A printer: its queued jobs are canceled and its sent-but-unreported jobs closed the same way; the printer row is deleted; its station stays in service.
    • Refused, per record: a station (or a printer on a station) that holds a live credential, shows as ONLINE or PAUSED and has jobs in CLAIMED, RECEIVED or SUBMITTING. Wait, or use Disconnect or Revoke access first. A record that is gone or not the caller's is reported and skipped. The other records of the selection are still deleted.
    • Jobs are never deleted. printing_job gains device_name, computer_name, printer_name and queue_name: the names the station and printer had when the job was submitted (written by the same INSERT; existing jobs are filled from the current rows; a delete fills any job that still lacks them). They are the read-only fields Print station, Computer name, Printer and Windows queue name on Print jobs, and the first two replace the record links in the default columns. device_id, printer_id and printer_row_id keep the identifiers the deleted records had; they are no longer references that must resolve, so the foreign key from printing_job.device_id to printing_device is dropped.
    • A computer enrolled again is a new station with a new identifier. A queue that a station still shares is reported by its next inventory and comes back as a new printer, exposed and not approved. Old jobs are not attached to either.
    • Everything a deleted station sends afterwards (heartbeat, inventory, claim, content, event, control poll, control receipt) and every management action on it is answered 404 Device not found; its token no longer authenticates (401). Print again on one of its jobs is refused with the reason.
    • New table printing_deletion, read-only as Deletion history in the Printing app: one row per deleted station and per deleted printer with what it was (kind, name, computer name, queue name, facility, the identifiers it had), who deleted it (actor), when (deleted_time, a UTC timestamp) and what went with it (printers, queued jobs canceled, jobs closed as unknown, jobs kept). It has no reference to the deleted rows.
    • New permission printing_delete (PrintingDeletePermission). Both actions require printing_delete and printing_admin; printing_admin alone does not delete and is not offered the actions. A host grants it where it grants the others.
    • Remove printers, Remove print station and Revoke print station access are unchanged (same process names, printing_admin). Remove still archives (removed_at, record kept); a removed record can be deleted.
  • A grouped read is a read. PrintingReadPermission let a query, a get and a count through and refused everything else as a write, so an aggregate ("print jobs by format") was refused for everybody, administrators included, wherever permissions are enforced. It now accepts AggregateInput and ExportInput with exactly the permission a query needs (printing_operator or printing_admin on stations and printers, printing_admin on the other tables). Inserts, updates and deletes through table actions stay refused.

  • Schema step for a host that owns the schema (PostgreSQL, one transaction):

    ALTER TABLE printing_job ADD COLUMN IF NOT EXISTS device_name VARCHAR(1024);
    ALTER TABLE printing_job ADD COLUMN IF NOT EXISTS computer_name VARCHAR(1024);
    ALTER TABLE printing_job ADD COLUMN IF NOT EXISTS printer_name VARCHAR(1024);
    ALTER TABLE printing_job ADD COLUMN IF NOT EXISTS queue_name VARCHAR(1024);
    UPDATE printing_job j SET device_name = d.name, computer_name = d.computer_name
      FROM printing_device d WHERE d.id = j.device_id AND j.device_name IS NULL;
    UPDATE printing_job j SET printer_name = p.name, queue_name = p.queue_name
      FROM printing_printer p WHERE p.id = j.printer_row_id AND j.printer_name IS NULL;
    ALTER TABLE printing_job DROP CONSTRAINT IF EXISTS printing_job_device_id_fkey;
    CREATE TABLE IF NOT EXISTS printing_deletion (id VARCHAR(36) PRIMARY KEY, tenant_id VARCHAR(200) NOT NULL,
      kind VARCHAR(20) NOT NULL, record_id VARCHAR(36) NOT NULL, name VARCHAR(1024) NOT NULL,
      computer_name VARCHAR(1024), queue_name VARCHAR(1024), facility_id VARCHAR(200), device_id VARCHAR(36),
      actor VARCHAR(200) NOT NULL, deleted_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
      printer_count INTEGER DEFAULT 0 NOT NULL, cancelled_job_count INTEGER DEFAULT 0 NOT NULL,
      unknown_job_count INTEGER DEFAULT 0 NOT NULL, kept_job_count INTEGER DEFAULT 0 NOT NULL);
    CREATE INDEX IF NOT EXISTS printing_deletion_tenant_idx ON printing_deletion(tenant_id, deleted_time, id);
    

    printing_job_device_id_fkey is the name PostgreSQL gives the inline REFERENCES of the original CREATE TABLE; a schema that named it otherwise drops that name. initialize() does all of this itself and finds the foreign key by metadata.

  • Without the step this version fails loudly: every submission is rolled back with the database's missing-column error for device_name, and QQQ cannot read Print jobs. If only the foreign key is left in place, deleting a station that has jobs is refused (409, naming the step) before anything is revoked. The previous library keeps working on the new schema; jobs it writes get their names when their station or printer is deleted.

1.2.0-RC.4: a station delete decides its refusal under the lock

No wire, contract or schema change; the schema step above is the same.

  • Fixed: deleting a station could close a job as OUTCOME_UNKNOWN that the station had just been sent and was about to print. deleteDevice decided "connected and printing" from a read without a lock, revoked the station, and only then locked and read again; a revoked station never counts as connected, so the second read could not refuse. A station that was idle at the first read and claimed a job before the revocation was deleted with that job in its hands.
  • The delete is now one transaction. It takes the tenant admission lock and then the station's row lock (the order the other paths use; a claim, a job event and a heartbeat take the station's row lock before they change anything), decides the refusal there, applies the REVOKE control on the same connection, closes the jobs, deletes the rows and commits once. A claim either commits before the delete locks, and the delete is refused (409, the station and its job untouched), or waits for the delete and is then answered 404 Device not found: the job was canceled by the delete and was never sent.
  • A delete that is refused or fails changes nothing. Before, a delete whose transaction failed left the station revoked; now the revocation rolls back with the rest and the delete can simply be run again. A station that an earlier version left revoked, or that was revoked with Revoke print station access, is deleted without a second revocation.
  • The station's connection is woken once, after the commit. The revocation of a delete no longer goes through the public PrintingService.deviceAction; a host that wraps that method to observe revocations sees a delete in printing_deletion instead.
  • deletePrinter did not have the window (it decides under the station's row lock and revokes nothing) and is unchanged.
  • HTTP API text restored. RC.3 changed one text an HTTP route returns, contrary to the note above: approving or unapproving a removed printer (PUT /printing/v1/admin/devices/{deviceId}/printers/{printerId}, 409) answers "Printer was removed; share it from the device again before changing its approval" again. The other text RC.3 changed ("Revoke the print station's access before removing it") is raised only by removeDevice, which no HTTP or agent route calls, and stays.
  • initialize() fills device_name / printer_name only on jobs whose station or printer still exists. Before, jobs of a deleted station or printer that have no name were rewritten on every start, and where such a job had a queue name but no printer name the queue name was set to null. They are still read on every start; there is no index on the name columns.
  • Deleting a station or a printer writes the names only onto its jobs that lack one, so a long job history is not rewritten while the tenant's admission lock is held.
  • Tests added for: the claim that lands just before the delete locks (station and printer) and the claim in flight that the delete waits for; a printer refused while it prints on a connected station; an already revoked station; a failed transaction rolled back and retried; the foreign-key violation (23503) named as the missing schema step; the final step refusing a record that passed the review; the review naming a record that went away or conflicts; a grouped read of print jobs counting the caller's tenant only. They run on the in-memory database of the test suite; the build has no PostgreSQL driver or container support, so the delete and the schema step are not exercised on PostgreSQL here.

1.1.2

Runtime and publication move to the QRun-IO Gitea Maven registry; no printing contract change (extension 2.4.0, wire v1, subprotocol unchanged). The server now runs on QQQ 4.1.0-RC.22, published only in the Gitea registry (it carries the session-logout, process-identity and scoped-session fixes the Forge cohort depends on). CI moves from the qrun-release caller and trusted publisher to the shared ci-workflows Maven workflow, which resolves QQQ from the Gitea registry with the job token and publishes vX.Y.Z tags straight to it. The ui-demo frontend (qqq-frontend-next 1.0.0-RC.37) was mirrored into the registry byte-for-byte from Central, so every QRun artifact resolves from Gitea; third-party dependencies still resolve through the Maven Central mirror until the registry proxies them. Same test gate (-Pui-demo verify).

1.1.1

Realtime upgrade fix; no contract change. The WebSocket callbacks read the device identifier from the socket context's headers, which are matched case-sensitively, while the HTTP upgrade matches them case-insensitively. Behind an ingress that lowercases header names the device resolved to null on connect and the connection failed before WELCOME (Forge had carried a local patch for this on 1.0.0). The device is now resolved once on the authorized upgrade and carried on the connection; a regression test connects with lowercase header names and verifies WELCOME, an acknowledged inventory, the PRINTERS push and socket replacement.

1.1.0

Native printing extension 2.4.0 (additive; wire payload v1 and the agent subprotocol qqq-printing.v1 are unchanged). Pairs with protocol 1.1.0 and Windows connector 1.2.1; 1.0.x connectors keep working unchanged.

  • New HELLO and HTTP control-poll capability printer-approval-v1 (HELLO accepts up to five capabilities, the control poll up to four; it is stored in the device's control capabilities like the others).
  • New server-to-client realtime frame PRINTERS: { "printers": [ { "printerId", "enabled" } ] }, the complete approval state of every printer in the device's latest accepted inventory. Sent only to connections that advertised the capability, after each accepted INVENTORY's ACK and whenever a printer's approval changes through any path (admin route, native approve/disable/set-approval processes, PrintingService.enablePrinter). An empty inventory yields an empty list; other connections never receive the frame.
  • PrintingService.printerApprovals(deviceId) exposes the same list to host code.
  • Remove printers and devices without losing history. New nullable removed_at columns on printing_printer and printing_device (read-only field Removed at on both tables, added by the idempotent migration). Remove printers (printingRemoveSelectedPrinters, on a printer record or a selection) and PrintingService.removePrinter(deviceId, printerId) set removed_at, clear approval and exposure, and drop the printer from the Printers default view (add the Removed at is empty filter), the operator catalog, claims and submissions, the device/printer selectors and the PRINTERS frame, which is pushed again on removal. A queue that the device shares again returns exposed but not approved. Remove device (printingRemoveDevices, confirmation required) revokes access through the existing REVOKE control, then PrintingService.removeDevice(deviceId) archives the device and all of its printers; an already revoked device is archived without a new command, and a removed device cannot be restored. Retention cleanup hard-deletes removed printers that no job references and removed devices with no jobs and no printers, bounded by the run's batch size.
  • Regenerated OpenAPI fragment (printing.yaml): semantic version 2.4.0 and the extended ControlPollRequest capability enumeration. No authorization, queue, replay or document behavior changed.

1.0.0

Product version 1.0.0 includes record, table-list and text print processes, PDF rendering with headers and footers, the least-privilege operator catalog, reported Windows queue-default media (printer-media-v1) and the opt-in retained-source facade. Native printing stays 2.3.0 with wire payload v1; 1.0 is a product version, not a wire change, and it pairs with Windows connector 1.0.1 and protocol 1.0.0. The QQQ dependency is the published 4.1.0-RC.21 candidate, which provides native session login.

0.4.0-rc.1

Printing extension 2.3.0 (additive; wire payload v1 and the agent subprotocol are unchanged). 2.1.0 added the record, table-list and text print processes; 2.2.0 added printingRenderToPdf, the header and footer values on every document and content sections; 2.3.0 adds the least-privilege operator catalog/submission/request lookup, reported Windows queue-default media (printer-media-v1) and the opt-in retained-source facade. Clients should offer a feature when its process is in the metadata, not by comparing versions. An agent that does not advertise printer-media-v1 stays a three-capability peer and cannot claim a job carrying a media selection, whatever extension version it discovers.

  • Built-in test prints add 4 x 1 and 4 x 2 inch labels (203/300/600 dpi) beside the unchanged 4 x 6 path: compact PDF and ZPL layouts, and for short PDF labels an explicit actual-size job paper (paperWidthMm/paperHeightMm, a complete positive pair up to 2000 mm, never combined with a media profile). 4 x 6 and page profiles keep using the queue default.
  • Strict source standards: Error Prone 2.51.0 and NullAway run as errors with -Werror on main and test sources, with honest @Nullable contracts, strict source headers and development-fixture style gates. No authorization, retained-source, replay or queue behavior was changed by this work.
  • Realtime shutdown now waits, with a bound, for channel workers that are already running before the host's durable state is released. Previously the executor was only told to stop, so a late drain could query a stopped file database and recreate it empty (found as a temporary-directory cleanup error in a full test run).

Observed locally on the merged candidate: full -Pui-demo verify 189 reported, 188 executed, one intentional opt-in workload skip, zero failures/errors, zero Checkstyle violations; five consecutive focused realtime runs passed. Hosted CI on the final signed head, publication and consumer qualification have not run; no physical output is claimed.

  • The app's header and footer on every page of every document (record sheets, table lists, text and content pages): app name, title or breadcrumb, server, user and printed-at at the top; the client's footer (e.g. qqq ui 1.0.1 · server), a list's filter and sort, and "Page n of m" at the bottom. Clients send headerApp, headerTitle, headerServer, headerUser, headerPrintedAt, footerLeft and footerNote; missing ones default to the instance branding, the session user, the current instant and "QQQ printing".
  • Content documents: printingPrintText and printingRenderToPdf (TEXT) take sections (JSON [{title, lines}]), a screen's panels without navigation or borders, rendered as headings with their lines wrapped to the page width.
  • Save as PDF: printingRenderToPdf renders the document a print would carry - a record sheet (kind RECORD, tableName, recordId), a table list (LIST, the printingPrintTableList values) or a text page (TEXT) - and returns it to the caller as the standard process download (downloadFileName, serverFilePath) instead of queueing a job. No printer is needed; reads enforce the caller's permissions, and a list over the limit says so in printingMessage. Clients offer it as "Save as PDF…" in their printer pickers.
  • Print anything: PrintingRecordProcesses.registerAll(instance) adds Print to every table that can be read one record at a time (call it after the host's tables are in the instance). Hidden tables, tables without a primary key or TABLE_GET, and tables carrying PrintingTableSettings().withPrintable(false) are skipped.
  • The record sheet now follows the table's metadata: field sections in tier order with their labels, remaining fields under "Other fields", and a summary of each child list (association): its label, row count and first ten rows.
  • New printingPrintTableList: a table list (grid) of any readable table with the caller's columns (in order) and widths, filter and sort, either the whole result (scope ALL, the default: read a thousand rows at a time up to maxRows, 10,000 by default and at most; a larger result prints its first rows and says so in the header and in printingMessage) or the current page (PAGE, the filter's skip/limit). Letter or A4; orientation AUTO (landscape when the columns need it), portrait or landscape. The header repeats on every page; the page header carries the table, the rows printed of the total, the filter text and the sort, and the footer the table, time, user and "Page n of m". printingRowCount reports the matching rows before printing.
  • New printingPrintText: any text (a capture of a terminal screen) as a monospace PDF, wrapped and broken across pages.
  • All documents share one page writer with "Page n of m" footers and a running title on later pages. Table and record reads enforce the user's permissions.

0.3.1-SNAPSHOT

  • Add record printing: PrintingRecordProcesses.register(instance, table) adds a Print process (printingPrintRecords_{table}) to a host table's records and selections. It asks for an approved, exposed PDF printer and a copy count, shows the standard review, then renders each selected record as a Letter record sheet (PrintingRecordDocuments: heading, visible non-binary fields with display values, wrapped and continued across pages) and queues one PDF job per record. The queued job IDs are returned in the process value printingJobIds.
  • Fix selection actions whose first step is a form (Print a test page, Print a test label on selected printers, and Print): over the UI API the record selection reaches only the init call, so the review answered "No records were selected". The prepare step now keeps the selection as queryFilterJson.

0.3.0-rc.1 — 2026-10-31

Upgrade the server before the matching Windows connector. Native printing API 2.0 requires synchronized authenticated WSS; old native clients cannot claim without it, and there is no polling fallback. Wire payloads remain v1.

  • Add client-initiated realtime WebSocket delivery under native printing API 2.0, with current QQQ session and record permissions revalidated during connection use.
  • Deliver desired state, device commands, inventory, lifecycle events and receipts over the socket; native claims require acknowledged current settings and state.
  • Stream retained documents independently from controls and expose document, concurrency, timing, admission and retention batch limits in native admin forms.
  • Preserve durable job/command identities across reconnects. Installed Windows realtime and physical-output acceptance remain separate from source/test gates.

0.2.0-rc.1 — 2026-10-31

  • Add native device/table selection actions for pause, resume, disconnect, reconnect, revocation, inventory refresh and queued-job clearing, plus exact owned Windows-job cancellation and bounded command history.
  • Show device presence, desired versus applied control state, related printers, jobs and queue; include connection/control issues in the printing dashboard.
  • Register semantic printing extension 1.0.0 at /qqq/v1/qbit/printing, using standard QQQ user sessions and secondary device proof with current native record/process permissions. Legacy bearer transport remains explicit compatibility.
  • Preserve journal/history and distinguish accepted commands from confirmed execution. Shared-core/provider native authentication acceptance and installed candidate qualification remain required before deployment claims.
  • Explicitly enforce current native permissions during selection counts and extraction, including callbacks and query selections. Pass 110 Java tests and 25 real .NET client/native QQQ interoperability checks without print jobs, including rejection of legacy enrollment and bound-token downgrade attempts.
  • Reject account-bound device tokens on legacy routes and disable legacy enrollment by default. Legacy-only hosts opt in explicitly; existing unbound devices retain migration compatibility.

0.1.4-rc.1 — 2026-10-31

  • Use the official Query dog mascot on every built-in PDF and ZPL diagnostic profile, preserving the original artwork and aspect ratio. Full local and hosted gates pass 73 tests with zero Checkstyle violations; PDF previews and reconstructed thermal graphics pass visual review. Local native deployment is accepted; physical output from the updated Query diagnostics remains unconfirmed.
  • Add an authenticated loopback QQQ host that shares durable paired devices/jobs with native screens and retention scheduling, preserving the separate synthetic UI demo.
  • Make native enrollment and test-print execution replay-safe and retain known duplicate-output risk after later failures.
  • Strictly validate print options, inventory formats/status and event bounds before persistence; recheck current queue format support at claim time.
  • Add dedicated S3 document storage through the host's QQQ storage API, tenant object namespaces and immutable destination/reference checks.
  • Bind S3 documents to the reviewed native key layout and platform separator using unambiguous ordered identity components, including multiline base paths; reject incompatible or old experimental bindings before historical object access and preserve cleanup accounting. Validate new retry sources before reserving uploads while retaining replay and inline database compatibility. Portable upstream keys and historical-object migration remain separate work.
  • Add admin retention settings with a 365-day default, reviewed cleanup and bounded scheduled maintenance; preserve job history and uncertain work.
  • Use named facility choices during enrollment and revalidate them under the current user's tenant/record permissions.
  • Require release security/CodeQL gates, verified artifact promotion and build provenance; report the real packaged QBit version.
  • Enforce configurable atomic queue/storage/rate budgets and cumulative printer limits, with bounded management pagination and stable replay at capacity.
  • Consume bounded claim/cancel request bodies before mutation so a persistent HTTP connection remains usable for the subsequent status event.
  • Pin H2 2.4.240 to fix close-time compaction data loss; verify file-backed schema/settings across cold reopens and document closed-backup upgrade qualification.
  • Verify the exact frontend-inclusive runtime inventory in Maven, Trivy and OSV; fail on malformed or incomplete scanner evidence.
  • Include repository LICENSE and NOTICE under META-INF using standard Maven resources; preserve font license, provenance and diagnostic assets.
  • The S3 safeguard checkpoint passed 72 tests with zero failures/errors/skips and Checkstyle violations, including incompatible S3 bindings, multiline prefix retargeting, retry accounting and inline document compatibility. Actual cross-platform refusal preserves objects and accounting with zero SDK requests. Required build/security/CodeQL/dependency gates pass with the approved scoped H2 record. Qualified artifacts passed isolated graceful/cold-restart checks and local paired-host acceptance with native authentication, preserved records, automatic connector reconnection and 365-day retention. No live S3 migration or remote deployment is claimed.
  • Publish the JAR, POM and SHA256SUMS after release run 41190650664 passed all gates. Verify checksums and GitHub attestations for all three assets against the release tag and source; all packaged entry contents match the reviewed Query CI artifact.

0.1.3-rc.1 — 2026-10-30

  • Shared QQQ-branded warehouse test pages and labels across native record/table actions and device requests.
  • Bundle licensed JetBrains Mono Bold; embed PDF glyphs and use bounded ZPL graphics for consistent identifier readability.
  • Add Letter/A4 and 4 × 6 profile options with explicit 203/300/600-dpi label resolution, compatible defaults and validation.
  • Add font/media, deterministic generation, ZPL bitmap reconstruction and operator action regression coverage.

0.1.2-rc.1 — 2026-10-30

  • Synchronize client-managed device/printer display names, descriptions and locations; preserve physical computer/queue names and stable identifiers.
  • Add read-only native profile fields, longer names and additive schema migration/backfill.
  • Bound queue claims and history reads, avoid loading document payloads for metadata, and provide keyset-paged admin history.
  • Verify 10,000 synthetic jobs, long-text profiles, migration, explicit clearing and tenant isolation (22 tests, zero Checkstyle violations).

0.1.1-rc.1 — 2026-10-29

  • Device-scoped test print, explicit reprint and pre-dispatch cancellation routes.
  • New-job reprints with stable action idempotency and duplicate-risk consent for spooled/uncertain originals.
  • Native job table and record actions with review screens, readable selections, tenant/permission checks and state revalidation.
  • Nineteen tests and zero Checkstyle violations, including authenticated HTTP operator actions and native multi-selection workflows.

0.1.0-rc.1 — 2026-10-29

Initial prerelease for QQQ 4.1+, verified against QQQ 4.1.0-RC.20.

  • Durable JDBC enrollment, device credentials and print queue.
  • Printer inventory with separate local exposure (available) and administrator approval (enabled); withdrawn queues cannot receive new claims.
  • PDF/ZPL submission, business idempotency, authenticated claims/content and replay-safe status events.
  • Namespaced QQQ Devices/Printers/Jobs metadata with friendly relationships, status choices and operational default columns.
  • Tenant-scoped dependent printer selectors, native table/record actions with review/result screens, and guarded enrollment.
  • Scoped Printing home dashboard and a durable enqueue effect target.
  • Legacy local print_* data migrates to printing_*; job-to-printer relationships are populated without changing the wire contract.
  • Loopback protocol server and separate synthetic native QQQ frontend demo.
  • Sixteen automated tests, Checkstyle gate and pinned shared protocol fixtures.
  • GitHub CI plus JAR/POM/checksum prerelease artifacts.

The development connector and installed LocalSystem service each printed a laser test page confirmed by the user. Zebra hardware, production signing/deployment, richer destination management and recovery workflows remain follow-up work.