Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5639a98be
|
||
|
|
c31a8f5f33
|
||
|
|
8d5b72302c
|
||
|
|
3c39a6af1b
|
||
|
|
c761eca970
|
||
|
|
f1ed178355
|
||
|
|
f10cc03d9e
|
||
|
|
454410081d
|
||
|
|
2a561815fc
|
||
|
|
e7721bdf90
|
||
|
|
c197c5acab
|
||
|
|
ba7fdc23d0
|
||
|
|
ab51f8da06
|
||
|
|
50d643d959
|
||
|
|
102fde779c
|
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# Shared QRun Maven CI/CD (KofTwentyTwo/ci-workflows): every push verifies; vX.Y.Z tags publish the
|
||||
# tested JAR/POM to the QRun-IO Gitea Maven registry. QQQ resolves from that registry with the job token.
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
maven:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||
with:
|
||||
maven-args: -Pui-demo
|
||||
secrets: inherit
|
||||
@@ -1,26 +0,0 @@
|
||||
# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository.
|
||||
name: qrun-release
|
||||
on:
|
||||
push: {}
|
||||
workflow_dispatch: {}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
jobs:
|
||||
release:
|
||||
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-maven.yaml@qrun-release-v1
|
||||
with:
|
||||
runner: linux-small
|
||||
maven-profiles: ui-demo
|
||||
fixture-repository: QRun-IO/qqq-print-protocol
|
||||
fixture-ref: 42c0d249f81395f00408ec0f92638dcbbf5deb03
|
||||
fixture-path: qqq-print-protocol
|
||||
postpublish:
|
||||
if: ${{ needs.release.outputs.capture == 'true' }}
|
||||
needs: release
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1
|
||||
with:
|
||||
runner: linux-small
|
||||
+24
-1
@@ -1,10 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## 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.0; 1.0.x connectors keep working unchanged.
|
||||
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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# QQQ Printing
|
||||
|
||||
Cloud printing for QQQ 4.1+, paired with the [Windows AppKit connector](https://git.kof22.com/QRun-IO/qqq-print-agent) 1.2.0 and the [shared protocol](https://git.kof22.com/QRun-IO/qqq-print-protocol) 1.1.0 (1.0.1 / 1.0.0 remain compatible). The Windows connector opens an outbound, authenticated two-way WebSocket; documents download separately over HTTPS. The cloud needs no inbound route into the facility. Version **1.1.0** supports native printing **2.4**, wire payload v1, real-time jobs and controls, native browser sign-in through QQQ, and pushes each exposed printer's approval to connectors that advertise `printer-approval-v1`. Upgrade the server before the matching Windows connector.
|
||||
Cloud printing for QQQ 4.1+, paired with the [Windows AppKit connector](https://git.kof22.com/QRun-IO/qqq-print-agent) 1.2.1 and the [shared protocol](https://git.kof22.com/QRun-IO/qqq-print-protocol) 1.1.0 (1.0.1 / 1.0.0 remain compatible). The Windows connector opens an outbound, authenticated two-way WebSocket; documents download separately over HTTPS. The cloud needs no inbound route into the facility. Version **1.1.0** supports native printing **2.4**, wire payload v1, real-time jobs and controls, native browser sign-in through QQQ, and pushes each exposed printer's approval to connectors that advertise `printer-approval-v1`. Upgrade the server before the matching Windows connector.
|
||||
|
||||
**Released:** [QQQ Printing 1.0.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.0.0), with a library JAR, matching POM and `SHA256SUMS`. The same JAR/POM are published in the QRun-IO Gitea Maven registry. Gitea requires sign-in for downloads. The QBit itself is not published to Maven Central and is not a standalone executable/fat JAR. See [installation and release access](docs/RELEASES.md).
|
||||
**Released:** [QQQ Printing 1.1.1](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.1), with a library JAR, matching POM and `SHA256SUMS` ([1.1.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.0) is superseded by the realtime upgrade fix; [1.0.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.0.0) remains available for rollback). The same JAR/POM are published in the QRun-IO Gitea Maven registry. Gitea requires sign-in for downloads. The QBit itself is not published to Maven Central and is not a standalone executable/fat JAR. See [installation and release access](docs/RELEASES.md).
|
||||
|
||||
**Runtime:** Java 21 and QQQ **4.1.0-RC.21**, published on Maven Central with native session login. The optional native UI demo uses QQQ frontend **1.0.0-RC.37**. Product version 1.1.0, native extension 2.4.0 and wire payload v1 are separate version contracts.
|
||||
**Runtime:** Java 21 and QQQ **4.1.0-RC.22**, published in the QRun-IO Gitea Maven registry (not on Maven Central) with native session login. The optional native UI demo uses QQQ frontend **1.0.0-RC.37**, also served from that registry. Product version 1.1.2, native extension 2.4.0 and wire payload v1 are separate version contracts.
|
||||
|
||||
Implemented: single-use enrollment, device credentials, inventory/approval, PDF/ZPL submission, durable JDBC queue, idempotency, claim/content/events, native QQQ setup/test-print processes, read-only device/printer/job tables and a durable enqueue effect target.
|
||||
|
||||
@@ -29,7 +29,7 @@ Java/.NET WebSocket jobs and S3 checks without an installed connector or printer
|
||||
|
||||
## Build and verify
|
||||
|
||||
Install Java 21. Clone this repository and `qqq-print-protocol` as siblings from Gitea. Check out protocol commit `42c0d249f81395f00408ec0f92638dcbbf5deb03`, the immutable fixture ref in `.gitea/workflows/qrun-release.yaml`. Use the checked-in wrapper (Maven 3.9.11).
|
||||
Install Java 21. Clone this repository and `qqq-print-protocol` as siblings from Gitea. Check out protocol commit `5005f3c9e27053b6c327bb923695012832c0fdcc`, the immutable fixture ref in `.gitea/workflows/qrun-release.yaml`. Use the checked-in wrapper (Maven 3.9.11).
|
||||
|
||||
```powershell
|
||||
.\mvnw.cmd -Pui-demo verify
|
||||
@@ -79,7 +79,7 @@ The local setup uses the same native authorization and transport paths as an int
|
||||
Use the [authenticated Gitea Maven registry](docs/RELEASES.md#use-the-maven-registry), or download the release JAR, matching POM and `SHA256SUMS` and verify both files. For a local installation of the downloaded artifacts:
|
||||
|
||||
```powershell
|
||||
.\mvnw.cmd install:install-file '-Dfile=qbit-printing-1.0.0.jar' '-DpomFile=qbit-printing-1.0.0.pom'
|
||||
.\mvnw.cmd install:install-file '-Dfile=qbit-printing-1.1.2.jar' '-DpomFile=qbit-printing-1.1.2.pom'
|
||||
```
|
||||
|
||||
After the artifact is available in your configured repository, add:
|
||||
@@ -88,7 +88,7 @@ After the artifact is available in your configured repository, add:
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-printing</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@@ -129,7 +129,7 @@ Removing is archiving, not deleting. **Remove printers** sets **Removed at** on
|
||||
|
||||
Metadata names follow QBit conventions: tables and data sources use `printing_*`, while processes/widgets use `printing...`. The initial namespace is fixed to `printing`. The QBit preserves the host's `QBrandingMetaData`; customer identity belongs in the host branding, not a custom QBit build.
|
||||
|
||||
Two independent settings govern dispatch: `available` means the queue is present in the latest locally selected inventory; `enabled` means a QQQ administrator approved it. New submissions and claims require both. A connector whose HELLO advertises `printer-approval-v1` (extension 2.4.0, connector 1.2.0) receives a `PRINTERS` frame listing `{ printerId, enabled }` for every queue in its latest accepted inventory after each accepted inventory and whenever an approval changes through any path; older connectors never receive it and keep reading the operator catalog. Removing a queue from local selection makes it unavailable without deleting its approval/history; removing a printer in QQQ additionally drops it from the frame until the device shares it again. An empty inventory withdraws all queues. Withdrawing a queue cannot retract a job already accepted by Windows.
|
||||
Two independent settings govern dispatch: `available` means the queue is present in the latest locally selected inventory; `enabled` means a QQQ administrator approved it. New submissions and claims require both. A connector whose HELLO advertises `printer-approval-v1` (extension 2.4.0, connector 1.2.1) receives a `PRINTERS` frame listing `{ printerId, enabled }` for every queue in its latest accepted inventory after each accepted inventory and whenever an approval changes through any path; older connectors never receive it and keep reading the operator catalog. Removing a queue from local selection makes it unavailable without deleting its approval/history; removing a printer in QQQ additionally drops it from the frame until the device shares it again. An empty inventory withdraws all queues. Withdrawing a queue cannot retract a job already accepted by Windows.
|
||||
|
||||
Application code calls `printing.submit(JSONObject)` with the shared `SubmitJobRequest` fields. A stable business idempotency key returns the same job when retried; changing document/options/target under the same key fails. PDF is sent as bytes encoded once in base64; ZPL is UTF-8 printer-ready bytes. Default decoded limits are 100 MiB for PDF and 20 MiB for ZPL, both configurable in the native Printing connection and limits process. Queue approval and format compatibility are checked before insertion.
|
||||
|
||||
@@ -167,7 +167,7 @@ The user confirmed physical laser PDF and Zebra ZPL/PDF output during developmen
|
||||
|
||||
Gitea is the source, pull-request, CI and release authority. The thin `.gitea/workflows/qrun-release.yaml` delegates to the protected `qrun-release-v1` channel with Java 21, the Maven wrapper, `ui-demo` and the immutable protocol fixture checkout. Required verify, secrets and gate checks precede merge; successful main builds capture the tested Maven reactor for the trusted registry publisher. Maintainers publish the corresponding tagged Gitea release from those verified bytes. See [release operations](docs/RELEASES.md#maintainer-release-path).
|
||||
|
||||
GitHub is a shadow/archive. Its Actions were retired for this repository on 2026-11-04; checked-in `.github` workflows describe the former pipeline and do not publish current printing releases. The separate Windows repository retains its signing/update-feed workflow. The 1.0.0 server release does not claim GitHub attestations or Maven Central publication. The JAR includes LICENSE, NOTICE and font/diagnostic asset licenses; publication does not deploy a QQQ host or print to hardware.
|
||||
GitHub is a shadow/archive. Its Actions were retired for this repository on 2026-11-04; checked-in `.github` workflows describe the former pipeline and do not publish current printing releases. The separate Windows repository retains its signing/update-feed workflow. The 1.0.0 and 1.1.0 server releases do not claim GitHub attestations or Maven Central publication. The JAR includes LICENSE, NOTICE and font/diagnostic asset licenses; publication does not deploy a QQQ host or print to hardware.
|
||||
|
||||
## Printing home
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
# Real-time printing host integration
|
||||
|
||||
QQQ Printing 1.1.0 (the released library is [1.0.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.0.0))
|
||||
QQQ Printing 1.1.1 (the released library is [1.1.1](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.1))
|
||||
implements native printing extension **2.4.0** (wire envelope v1), paired with Windows
|
||||
connector **1.2.0**; connector 1.0.1 remains compatible. Upgrade the server before the connector; a real-time connector
|
||||
connector **1.2.1**; connector 1.0.1 remains compatible. Upgrade the server before the connector; a real-time connector
|
||||
rejects a server that advertises only extension 1.x. There is no timed job/control
|
||||
polling fallback.
|
||||
|
||||
|
||||
+14
-8
@@ -1,6 +1,6 @@
|
||||
# Installation and release operations
|
||||
|
||||
The stable server library is [QQQ Printing 1.0.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.0.0), paired with protocol 1.0.0 and Windows connector 1.0.1. It requires Java 21 and the QQQ 4.1.0-RC.21 dependency tuple. Its native printing extension is 2.3.0; the WebSocket subprotocol remains `qqq-printing.v1`.
|
||||
The stable server library is [QQQ Printing 1.1.1](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.1) (1.1.0 is superseded: its realtime upgrade fails behind an ingress that lowercases header names), paired with protocol 1.1.0 and Windows connector 1.2.1 (1.0.x/1.1.x connectors keep working on the unchanged 2.3 behaviour). It requires Java 21 and the QQQ 4.1.0-RC.22 dependency tuple, which is published only in the QRun-IO Gitea Maven registry. Its native printing extension is 2.4.0; the WebSocket subprotocol remains `qqq-printing.v1`. The previous stable library, [1.0.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.0.0), remains available for rollback.
|
||||
|
||||
Gitea is primary for source, pull requests, CI, packages and server releases. This forge requires sign-in even for download links on public repositories. An HTML sign-in page is not an artifact. Use your existing approved forge identity; never commit its token or put it in a command line.
|
||||
|
||||
@@ -18,11 +18,11 @@ Configure the consuming application's existing Maven repository setup with repos
|
||||
</servers>
|
||||
```
|
||||
|
||||
Merge this entry into the existing settings; preserve other servers, mirrors and profiles. Supply the environment through your approved credential loader. The credential needs package read access. Add `com.kingsrook.qbits:qbit-printing:1.0.0` as shown in the [README](../README.md#add-to-a-qqq-application). QQQ's RC.21 dependencies resolve from Maven Central; the printing 1.0.0 library resolves from Gitea.
|
||||
Merge this entry into the existing settings; preserve other servers, mirrors and profiles. Supply the environment through your approved credential loader. The credential needs package read access. Add `com.kingsrook.qbits:qbit-printing:1.1.1` as shown in the [README](../README.md#add-to-a-qqq-application). QQQ 4.1.0-RC.22 and the printing library both resolve from this registry; third-party dependencies still come from the Maven Central mirror until the registry proxies them.
|
||||
|
||||
## Download and verify release files
|
||||
|
||||
Sign in to Gitea and download all three assets from the stable release: `qbit-printing-1.0.0.jar`, `qbit-printing-1.0.0.pom` and `SHA256SUMS`. In their download directory:
|
||||
Sign in to Gitea and download all three assets from the stable release: `qbit-printing-1.1.1.jar`, `qbit-printing-1.1.1.pom` and `SHA256SUMS`. In their download directory:
|
||||
|
||||
```powershell
|
||||
Get-Content ./SHA256SUMS | ForEach-Object {
|
||||
@@ -37,11 +37,17 @@ On Linux, use `sha256sum -c SHA256SUMS`; on macOS, use `shasum -a 256 -c SHA256S
|
||||
|
||||
## Maintainer release path
|
||||
|
||||
1. Update the POM version and active documentation on a branch in Gitea. Keep the immutable protocol fixture ref in `.gitea/workflows/qrun-release.yaml` aligned with the reviewed contract.
|
||||
2. Open a normal pull request and satisfy required CI checks. The caller delegates to the protected `qrun-release-v1` channel; shared CI changes belong to its owner, not an application-specific bypass.
|
||||
3. Merge normally and wait for the successful main run. Its `maven-reactor` artifact contains tested JAR/POM bytes, a source commit, manifest and checksums. The trusted publisher verifies that evidence before publishing immutable Maven packages. A passing build alone does not prove registry publication.
|
||||
4. Verify the exact version's registry JAR/POM against the captured bytes. Create `v<POM version>` at the qualified source without moving an existing tag. Create a draft Gitea release, attach the same JAR/POM and a flat `SHA256SUMS`, read them back with the approved forge identity, then publish. Reconcile identical existing artifacts; never replace different bytes under a released version.
|
||||
5. Record the source, successful run, artifact, release and verified hashes. Publication is separate from installing a host or accepting physical printer output.
|
||||
Since 1.1.2 the repository builds on the shared `KofTwentyTwo/ci-workflows` Maven workflow (`.gitea/workflows/build.yaml`, pinned to `@v1`): every push runs `-Pui-demo verify`; a `vX.Y.Z` tag runs the same tests once and deploys the JAR/POM to the QRun-IO Gitea Maven registry under exactly that version. The former caller's pinned protocol checkout is gone; `PrintingContractTest` reads the vendored copy in `src/test/resources/protocol-fixtures` (kept byte-identical to the reviewed protocol commit named in its README), which is also what local `verify` uses. Earlier releases (1.0.0, 1.1.0, 1.1.1) went through the protected `qrun-release-v1` channel and the trusted publisher; their evidence below stands.
|
||||
|
||||
1. Update the POM version (the tag must equal it), `qqq.version` if the runtime moves, and active documentation on a branch in Gitea. Open a normal pull request; the push build must be green.
|
||||
2. Merge normally and wait for the green main build (verify only; main never publishes).
|
||||
3. Create `v<POM version>` at the qualified main commit without moving an existing tag and push it. The tag build tests and deploys `com.kingsrook.qbits:qbit-printing:<version>` to the registry; a tag build that fails publishes nothing and the next attempt is the next patch version (tags are immutable).
|
||||
4. Read the registry JAR/POM back with the approved forge identity, create the Gitea release on the tag, attach the same JAR/POM and a flat `SHA256SUMS`, read them back, then publish. Never replace different bytes under a released version.
|
||||
5. Record the source, tag build, release and verified hashes here. Publication is separate from installing a host or accepting physical printer output.
|
||||
|
||||
For 1.1.1, source `2a561815fcec8b7c95e76bb4b36fba314f512027` (main; merge of PR 30, the realtime upgrade header-case fix), main run `5976` (verify, secrets scan, gate and post-publish succeeded), reactor artifact `4426`. The trusted publisher first refused the run because the 1.1.0 documentation merge had re-pinned the protocol fixture in `qrun-release.yaml`, changing the caller hash its enrollment verifies; the enrollment was re-reviewed in cluster-gitops (PRs 283 and 285) and the capture was then published unchanged. Registry files and release assets match the capture byte-for-byte. The JAR SHA-256 is `156720da6928f62d77101521a2953f6e1026d58d02e7fc7815a8c8fcbcedaefa`; the POM SHA-256 is `2fa451260a26f5b5ede30800d46c78050e223694d3d8dda640f9ca875d120ab0`.
|
||||
|
||||
For 1.1.0, source `eb2c16785d0723a47d4a22c68b8b04503108fc58` (main; merges of PR 25 printer approval and PR 28 printer/device removal), main run `5473` (verify, secrets scan, gate and post-publish all succeeded after the CI quota incident of the same afternoon), reactor artifact `4306`, release assets read back by the approved forge identity and matching the registry files byte-for-byte. The JAR SHA-256 is `3a51b7e7cf457cce5db16f138c4cc65b901730bda17ccaf1d3c714cf11d2bbf4`; the POM SHA-256 is `52e5a588e788bbe123dad574455493aea0a7df6cc473c78f15be09343048a5d4`.
|
||||
|
||||
For 1.0.0, source `2906c2bd5049924abd2114c8ef51a0ac9aac325e`, main run `3594`, reactor artifact `2741` and release `1356` were verified. Release assets and registry files matched byte-for-byte on authenticated readback. The JAR SHA-256 is `7ecb00e1f49272d6e885dc5f515bd4832aa52dd84e0613d9b6751f9e235a0961`; the POM SHA-256 is `8be7c30208a7fbb875dcebedb0ba6baf0e172aa0e7cf478a3d2863d1316ddf5f`.
|
||||
|
||||
|
||||
+11
-5
@@ -1,10 +1,16 @@
|
||||
# Session state — 2026-11-05
|
||||
# Session state — 2026-11-06
|
||||
|
||||
## In progress — 2026-11-05: printer approval over realtime (1.1.0 / extension 2.4.0)
|
||||
## In progress — 1.1.2: QQQ 4.1.0-RC.22 from the Gitea registry, shared CI
|
||||
|
||||
Branch `feat/printer-approval-20261105` adds the `printer-approval-v1` capability and the `PRINTERS` frame
|
||||
(see [CHANGELOG](../CHANGELOG.md) 1.1.0 and [real-time integration](REALTIME.md)). Product version is bumped to
|
||||
1.1.0 in the POM; nothing is tagged, released or published, and the released state below is unchanged.
|
||||
James's direction (2026-11-06): everything moves to the QRun-IO Gitea Maven registry; no more Maven Central for QRun artifacts. 1.1.1 was certified on QQQ 4.1.0-RC.21 from Central while Forge pins RC.22 (Gitea only), so 1.1.2 pins `qqq.version` 4.1.0-RC.22 and moves CI to the shared `ci-workflows` Maven workflow (the protected `qrun-release` channel resolves only Central and cannot build RC.22). No printing contract change. Gate: the push build's `-Pui-demo verify` on the Gitea registry; then tag v1.1.2, registry readback, Gitea release, handoff update. `qqq-frontend-next` 1.0.0-RC.37 (ui-demo profile) was mirrored into the registry byte-for-byte from Central (JAR `c9fa2eb2…`, POM `4761d2fd…`, verified against Central's SHA-1 and read back), so every QRun artifact now resolves from Gitea; only third-party dependencies still come through the Central mirror until the registry proxies them.
|
||||
|
||||
## Current — 1.1.1 released (2026-11-06)
|
||||
|
||||
[QQQ Printing 1.1.1](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.1) is published on primary Gitea and its Maven registry (`com.kingsrook.qbits:qbit-printing:1.1.1`). It supersedes 1.1.0: Forge's adoption qualification of 1.1.0 (`~/.cache/gg-forge-printing-cohort-20261106/HANDOFF.md`) reproduced a connection failure before WELCOME when the WebSocket upgrade headers arrive lowercase, because the socket callbacks read `X-QQQ-Device-Id` from the WebSocket context, whose header lookup is case-sensitive. 1.1.1 resolves the device once on the authorized HTTP upgrade and carries it as a connection attribute; `PrintingRealtimeTest.lowercaseUpgradeHeadersReachWelcomeAndFrames` fails on the old code and passes on the fix. No contract change: extension 2.4.0, wire v1, subprotocol unchanged. Source `2a561815fcec8b7c95e76bb4b36fba314f512027` (PR 30), main run `5976`, reactor artifact `4426`, release `1381`; JAR `156720da6928f62d77101521a2953f6e1026d58d02e7fc7815a8c8fcbcedaefa`, POM `2fa451260a26f5b5ede30800d46c78050e223694d3d8dda640f9ca875d120ab0`, read back against the capture and the registry. The publisher had refused the run until its enrollment hash was re-reviewed (cluster-gitops PRs 283 and 285) after the 1.1.0 docs merge re-pinned the protocol fixture in the release caller.
|
||||
|
||||
## Superseded — 1.1.0 released (2026-11-05)
|
||||
|
||||
[QQQ Printing 1.1.0](https://git.kof22.com/QRun-IO/qbit-printing/releases/tag/v1.1.0) is published on primary Gitea and its Maven registry: native printing extension **2.4.0** (`printer-approval-v1` capability and the `PRINTERS` frame) and the Remove printers / Remove device archives purged by retention. Source `eb2c16785d0723a47d4a22c68b8b04503108fc58`, main run `5473`, reactor artifact `4306`; JAR/POM hashes in [release operations](RELEASES.md). Pairs with [protocol 1.1.0](https://git.kof22.com/QRun-IO/qqq-print-protocol/releases/tag/v1.1.0); the Windows connector 1.2.1 is published (https://git.kof22.com/QRun-IO/qqq-print-agent/releases/tag/v1.2.1). The CI fixture pin now follows protocol 1.1.0 source `5005f3c9e27053b6c327bb923695012832c0fdcc`. Forge adoption is a separate, coordinated step; 1.0.0 stays available for rollback.
|
||||
|
||||
## Current — 1.0.0 released
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<parent><groupId>com.kingsrook</groupId><artifactId>qbit-build-parent</artifactId><version>2.0.0</version><relativePath/></parent>
|
||||
<groupId>com.kingsrook.qbits</groupId>
|
||||
<artifactId>qbit-printing</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.1.2</version>
|
||||
<name>QQQ Printing</name>
|
||||
<licenses><license><name>Apache License, Version 2.0</name><url>https://www.apache.org/licenses/LICENSE-2.0</url></license></licenses>
|
||||
<properties>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<qqq.version>4.1.0-RC.21</qqq.version>
|
||||
<qqq.version>4.1.0-RC.22</qqq.version>
|
||||
<maven.compiler.proc>none</maven.compiler.proc>
|
||||
</properties>
|
||||
<dependencyManagement><dependencies>
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -59,6 +60,8 @@ public final class PrintingRealtimeRoutes implements QJavalinRouteProviderInterf
|
||||
{
|
||||
public static final String PATH = "/qqq/v1/qbit/printing/device/realtime";
|
||||
public static final String SUBPROTOCOL = "qqq-printing.v1";
|
||||
/** Device identifier resolved on the authorized HTTP upgrade and carried to the WebSocket callbacks. */
|
||||
static final String DEVICE_ATTRIBUTE = "printingRealtimeDevice";
|
||||
private final PrintingService service;
|
||||
private final Map<String, Channel> channels = new ConcurrentHashMap<>();
|
||||
private final ExecutorService workers = Executors.newVirtualThreadPerTaskExecutor();
|
||||
@@ -97,6 +100,10 @@ public final class PrintingRealtimeRoutes implements QJavalinRouteProviderInterf
|
||||
{
|
||||
validateUpgrade(context);
|
||||
authorize(context);
|
||||
// The HTTP upgrade context resolves header names case-insensitively; the WebSocket context does not, and an ingress
|
||||
// that lowercases header names would otherwise leave the device null on connect. Resolve it once here, after
|
||||
// authorization, and carry it on the connection.
|
||||
context.attribute(DEVICE_ATTRIBUTE, Objects.requireNonNull(context.header(PrintingAgentAuthorization.DEVICE_HEADER), "authorized upgrade without a device header"));
|
||||
String cookieName = context.cookie("sessionId") != null && !context.cookie("sessionId").isBlank() ? "sessionId" : "sessionUUID";
|
||||
context.attribute("printingRealtimeCredential", new Credential(cookieName, context.cookie(cookieName), context.header(PrintingAgentAuthorization.TOKEN_HEADER), context.header("X-QQQ-Client")));
|
||||
}
|
||||
@@ -113,7 +120,7 @@ public final class PrintingRealtimeRoutes implements QJavalinRouteProviderInterf
|
||||
{
|
||||
socket.onConnect(context ->
|
||||
{
|
||||
String device = context.header(PrintingAgentAuthorization.DEVICE_HEADER);
|
||||
String device = Objects.requireNonNull(context.attribute(DEVICE_ATTRIBUTE), "WebSocket connected without the authorized device attribute");
|
||||
Channel next = new Channel(context, device, context.attribute("printingRealtimeCredential"));
|
||||
next.policy = service.realtimePolicy();
|
||||
Channel previous = channels.put(device, next);
|
||||
@@ -149,7 +156,8 @@ public final class PrintingRealtimeRoutes implements QJavalinRouteProviderInterf
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
private @Nullable Channel current(WsContext context)
|
||||
{
|
||||
Channel channel = channels.get(context.header(PrintingAgentAuthorization.DEVICE_HEADER));
|
||||
String device = context.attribute(DEVICE_ATTRIBUTE);
|
||||
Channel channel = device == null ? null : channels.get(device);
|
||||
return channel != null && channel.socket.session == context.session ? channel : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,55 @@ public class PrintingRealtimeTest
|
||||
Path directory;
|
||||
|
||||
|
||||
/** Lowercase upgrade header names (an ingress that normalizes them) must still resolve the device for every socket callback. */
|
||||
@Test
|
||||
void lowercaseUpgradeHeadersReachWelcomeAndFrames() throws Exception
|
||||
{
|
||||
String jdbc = "jdbc:h2:file:" + directory.resolve("lowercase-realtime").toString().replace('\\', '/');
|
||||
PrintingLocalHost host = new PrintingLocalHost(new PrintingLocalSettings(jdbc, "sa", "", "demo", 0, "synthetic-management-token-long-enough", "operator", "synthetic-realtime-password-1234"));
|
||||
AtomicReference<Javalin> app = new AtomicReference<>();
|
||||
QApplicationLauncher launcher = QApplicationLauncher.run(host, new QApplicationLauncherConfig().withRegisterShutdownHook(false).withServerCustomizer(server ->
|
||||
{
|
||||
host.configure(server, false);
|
||||
server.withJavalinConfigurationCustomizer(app::set);
|
||||
}));
|
||||
PrintingService service = Objects.requireNonNull(launcher.getQInstance().getQBits().values().stream().map(value -> value.getConfig()).filter(PrintingQBitConfig.class::isInstance)
|
||||
.map(PrintingQBitConfig.class::cast).findFirst().orElseThrow().getService(), "The printing service was not configured");
|
||||
String base = "http://127.0.0.1:" + Objects.requireNonNull(app.get(), "Test HTTP server was not started").port();
|
||||
JSONObject enrolled = service.enrollForUser(service.createEnrollment("default", "Lowercase fixture").put("deviceName", "Lowercase fixture").put("agentVersion", "test"), "operator");
|
||||
String device = enrolled.getString("deviceId");
|
||||
String proof = enrolled.getString("deviceToken");
|
||||
try(HttpClient http = HttpClient.newHttpClient())
|
||||
{
|
||||
String cookie = login(http, base);
|
||||
// An ingress that lowercases header names must not leave the device unresolved on connect: WELCOME, an acknowledged
|
||||
// inventory and the capability-gated PRINTERS push all work exactly as with the canonical spelling.
|
||||
try(PrintingSocketFixture lowercase = PrintingSocketFixture.open(http, base, cookie, device, proof, true))
|
||||
{
|
||||
lowercase.initialize(new JSONArray().put("realtime-v1").put("REFRESH_INVENTORY").put("CANCEL_SPOOL").put("printer-approval-v1"));
|
||||
lowercase.send("INVENTORY", "inventory-lower", new JSONObject().put("printers", new JSONArray().put(new JSONObject().put("printerId", "queue").put("name", "Synthetic queue")
|
||||
.put("driverName", "Synthetic").put("portName", "SYNTHETIC").put("status", "READY").put("formats", new JSONArray().put("PDF")))));
|
||||
assertEquals("inventory-lower", lowercase.take("ACK").getJSONObject("payload").getString("acknowledgedMessageId"));
|
||||
JSONObject printers = lowercase.take("PRINTERS").getJSONObject("payload");
|
||||
assertEquals("queue", printers.getJSONArray("printers").getJSONObject(0).getString("printerId"));
|
||||
assertFalse(printers.getJSONArray("printers").getJSONObject(0).getBoolean("enabled"));
|
||||
// A replacement socket with canonical spelling supersedes the lowercase one through the same device key.
|
||||
try(PrintingSocketFixture canonical = PrintingSocketFixture.connect(http, base, cookie, device, proof))
|
||||
{
|
||||
assertTrue(lowercase.closed.get(10, TimeUnit.SECONDS) > 0);
|
||||
canonical.send("PING", "ping-1", new JSONObject());
|
||||
canonical.take("PONG");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
launcher.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** An old client cannot ignore media intent, including after replacing a capable connection. */
|
||||
@Test
|
||||
void mediaCapabilityIsReplacedOnEveryAuthenticatedHello() throws Exception
|
||||
|
||||
@@ -55,11 +55,20 @@ final class PrintingSocketFixture implements WebSocket.Listener, AutoCloseable
|
||||
|
||||
/** Separate upgrade from HELLO so a commit in that interval can be tested deterministically. */
|
||||
static PrintingSocketFixture open(HttpClient client, String base, String cookie, String device, String proof) throws Exception
|
||||
{
|
||||
return open(client, base, cookie, device, proof, false);
|
||||
}
|
||||
|
||||
|
||||
/** Lowercase header names model an ingress that normalizes them, which the device lookup must survive. */
|
||||
static PrintingSocketFixture open(HttpClient client, String base, String cookie, String device, String proof, boolean lowercaseHeaders) throws Exception
|
||||
{
|
||||
PrintingSocketFixture fixture = new PrintingSocketFixture();
|
||||
String deviceHeader = lowercaseHeaders ? PrintingAgentAuthorization.DEVICE_HEADER.toLowerCase(java.util.Locale.ROOT) : PrintingAgentAuthorization.DEVICE_HEADER;
|
||||
String tokenHeader = lowercaseHeaders ? PrintingAgentAuthorization.TOKEN_HEADER.toLowerCase(java.util.Locale.ROOT) : PrintingAgentAuthorization.TOKEN_HEADER;
|
||||
fixture.socket = client.newWebSocketBuilder().subprotocols(PrintingRealtimeRoutes.SUBPROTOCOL).header("Origin", base)
|
||||
.header("X-QQQ-Client", "api/qqq-print-agent")
|
||||
.header("Cookie", cookie).header(PrintingAgentAuthorization.DEVICE_HEADER, device).header(PrintingAgentAuthorization.TOKEN_HEADER, proof)
|
||||
.header(lowercaseHeaders ? "x-qqq-client" : "X-QQQ-Client", "api/qqq-print-agent")
|
||||
.header("Cookie", cookie).header(deviceHeader, device).header(tokenHeader, proof)
|
||||
.buildAsync(URI.create(base.replace("http:", "ws:") + PrintingRealtimeRoutes.PATH), fixture).get(10, TimeUnit.SECONDS);
|
||||
return fixture;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Protocol fixtures used by PrintingContractTest
|
||||
|
||||
Unaltered copies of files from the `fixtures/` directory of the canonical `qqq-print-protocol` repository
|
||||
at commit `42c0d249f81395f00408ec0f92638dcbbf5deb03` (the commit this source pins), so the contract tests run
|
||||
at commit `5005f3c9e27053b6c327bb923695012832c0fdcc` (protocol 1.1.0, the commit this source pins; the files below are unchanged since `42c0d249f81395f00408ec0f92638dcbbf5deb03`), so the contract tests run
|
||||
without a sibling checkout. Only the files the test reads are copied:
|
||||
|
||||
- `action-refresh-inventory.request.json`
|
||||
|
||||
Reference in new issue
Block a user