Problem Statement
From 1.3.0 onwards, the registration client's upgrade mechanism needs to be reworked. In versions <1.3.0, softwareUpdateHandler downloaded artifacts directly into lib/ (blocking, foreground) and run.bat simply picked up the new JARs on the next launch. This model is limited:
Downloads were foreground and blocking, with no resumable support — an interrupted download or lost network meant restarting the fetch from scratch.
The update handler made placement decisions and wrote directly into lib/, leaving no single point of control over what happens to downloaded artifacts.
Integrity was tracked through a single manifest, with no clean separation between version/upgrade orchestration and per-file integrity verification.
There was no consistent, signature-verified, recoverable flow for both full upgrades and lib-only patch upgrades.
The revamp introduces a two-phase, manifest-driven model: softwareUpdateHandler is reduced to a pure download-and-record role (downloads artifacts to .artifacts/, updates ./MANIFEST.MF, prompts restart), and _launcher.jar becomes the sole entry point that, on restart, verifies signatures, compares versions across a dual-manifest model, and orchestrates the correct upgrade path (full or lib-only patch) with resumable downloads and integrity-based recovery.
Note: The Java 11 → Java 21 JRE migration is already delivered. This story focuses only on the upgrade mechanism.
Goals
- Reduce softwareUpdateHandler to a pure download-and-record role: download artifacts to .artifacts/ (resumable, background, with progress) and update ./MANIFEST.MF. It never unzips, never copies to lib/, and never touches run.bat.
- Prompt the operator to restart only after all .artifacts/ downloads complete — no auto-restart.
- Make _launcher.jar the single decision point and entry point on every startup: verify the ./MANIFEST.MF signature, compare ./MANIFEST.MF vs lib/MANIFEST.MF, and select the correct path.
- Support a lib-only patch upgrade path: download and verify lib/MANIFEST.MF, resumable-download lib.zip, verify its hash, and unzip to .TEMP/; run.bat applies .TEMP/* → lib/ on the next operator-initiated start.
- Implement the dual-manifest model: root ./MANIFEST.MF drives version tracking and upgrade orchestration (no lib.zip entry); lib/MANIFEST.MF, delivered inside lib.zip, drives ClientPreLoader per-file integrity checks.
- Implement the signature/integrity verification flow with recovery Cases A–D (re-download on trusted-manifest local tamper; hard-abort on invalid or MITM signature).
- Make all upgrade downloads resumable (byte-range), so interrupted or network-lost downloads continue from the last byte on restart/reconnect.
- Always download lib.zip on-demand when a version difference is detected; never download anything directly to lib/.
- Clean up leftover upgrade artefacts on the first matched-version startup (step 6 cleanup).
- Update the build/server pipeline to sign both manifests, restructure the server manifest, and bundle lib/MANIFEST.MF inside lib.zip.
Non-Goals
- The Java 11 → Java 21 JRE migration and the native migration.exe / rollback.exe executables — already delivered; not in scope here.
- Modifying run.bat, run.exe, or anything in the application root by the upgrade handler (the immutable-launcher constraint stands; run.bat's existing .TEMP/ → lib/ copy behaviour is reused, not changed).
- Any auto-restart behaviour — the operator is always prompted.
- Changes to the application's functional/business behaviour beyond upgrade orchestration.
- Re-signing or re-keying infrastructure beyond producing the .sig artifacts described here.
Acceptance Criteria
AC1 — Download relocation & resumability
Given a version mismatch between ./MANIFEST.MF and maven-metadata.xml, when softwareUpdateHandler runs, then artifacts are downloaded to .artifacts/ (not lib/) using resumable, background downloads with progress indication.
AC2 — Handler invariant
Given softwareUpdateHandler completes, then it has only downloaded artifacts and updated ./MANIFEST.MF — it has not unzipped anything, copied to lib/, or modified run.bat.
AC3 — Restart prompt timing
Given downloads are in progress, then the operator is prompted to restart only after all .artifacts/ downloads complete; the application never auto-restarts.
AC4 — Single entry point
Given the application starts, then _launcher.jar's Initialization class is the sole entry point and registration-client.jar has no main() entry point.
AC5 — Signature verification on startup
Given startup, when _launcher.jar runs, then it verifies the ./MANIFEST.MF signature using the embedded public key before any upgrade decision.
AC6 — Version comparison & path selection
Given a valid signature, when _launcher.jar compares ./MANIFEST.MF vs lib/MANIFEST.MF, then: if versions match it proceeds to normal startup; if they differ it triggers the correct upgrade path.
AC7 — Lib-only patch upgrade path
Given versions differ and the JRE is the supported runtime, then _launcher.jar downloads and verifies lib/MANIFEST.MF (+ .sig) to .TEMP/, resumable-downloads lib.zip to .TEMP/lib.zip, verifies its hash against lib/MANIFEST.MF, unzips to .TEMP/, and prompts restart.
AC8 — Applying staged updates
Given staged files in .TEMP/, when the operator restarts via run.bat, then run.bat copies .TEMP/* → lib/, versions now match, and normal startup proceeds with ClientPreLoader verifying hashes against lib/MANIFEST.MF.
AC9 — Dual-manifest contract
Given the upgrade model, then root ./MANIFEST.MF is used for version tracking/orchestration and contains no lib.zip entry, while lib/MANIFEST.MF is delivered inside lib.zip and is used by ClientPreLoader for per-file hash verification.
AC10 — On-demand lib.zip
Given a version difference is detected, then lib.zip is always downloaded on-demand; nothing is downloaded directly to lib/ from 1.3.0 onwards.
AC11 — Case A (valid manifest sig, file hash fails)
Given ./MANIFEST.MF.sig (or lib/MANIFEST.MF.sig) is valid but a file hash fails, then the file is re-downloaded to .TEMP/, its hash re-verified, the operator is informed to restart, and run.bat (for lib/ files) or _launcher.jar (for root files) places it on restart.
AC12 — Case B (invalid manifest sig)
Given the manifest signature is invalid, then a security alert is shown, nothing is re-downloaded, and the JVM exits.
AC13 — Case C (missing manifest sig)
Given MANIFEST.MF.sig is missing, then it is downloaded from the upgrade server and its integrity checked; if verification fails, Case B handling applies.
AC14 — Resumable downloads
Given any upgrade download (artifacts, manifests, or lib.zip) is interrupted or loses network, when the operator restarts or reconnects, then the download resumes from the last byte rather than restarting.
AC15 — Post-upgrade cleanup
Given versions match on startup after a completed upgrade, then _launcher.jar removes leftover upgrade artefacts (e.g. .artifacts/ and other migration artefacts) before proceeding to normal startup.
AC16 — Server / build pipeline
Given the build pipeline, then configure.sh signs ./MANIFEST.MF and lib/MANIFEST.MF (producing .sig files), the server ./MANIFEST.MF excludes lib.zip, lib/MANIFEST.MF is bundled inside lib.zip, and all files under lib/** are zipped as lib.zip and hosted on the upgrade server.
AC17 — Headless safety
Given a headless environment, when an operator dialog would be shown, then _launcher.jar guards with GraphicsEnvironment.isHeadless() and degrades gracefully instead of throwing.
Sub-Tasks
Problem Statement
From 1.3.0 onwards, the registration client's upgrade mechanism needs to be reworked. In versions <1.3.0, softwareUpdateHandler downloaded artifacts directly into lib/ (blocking, foreground) and run.bat simply picked up the new JARs on the next launch. This model is limited:
Downloads were foreground and blocking, with no resumable support — an interrupted download or lost network meant restarting the fetch from scratch.
The update handler made placement decisions and wrote directly into lib/, leaving no single point of control over what happens to downloaded artifacts.
Integrity was tracked through a single manifest, with no clean separation between version/upgrade orchestration and per-file integrity verification.
There was no consistent, signature-verified, recoverable flow for both full upgrades and lib-only patch upgrades.
The revamp introduces a two-phase, manifest-driven model: softwareUpdateHandler is reduced to a pure download-and-record role (downloads artifacts to .artifacts/, updates ./MANIFEST.MF, prompts restart), and _launcher.jar becomes the sole entry point that, on restart, verifies signatures, compares versions across a dual-manifest model, and orchestrates the correct upgrade path (full or lib-only patch) with resumable downloads and integrity-based recovery.
Note: The Java 11 → Java 21 JRE migration is already delivered. This story focuses only on the upgrade mechanism.
Goals
Non-Goals
Acceptance Criteria
AC1 — Download relocation & resumability
Given a version mismatch between ./MANIFEST.MF and maven-metadata.xml, when softwareUpdateHandler runs, then artifacts are downloaded to .artifacts/ (not lib/) using resumable, background downloads with progress indication.
AC2 — Handler invariant
Given softwareUpdateHandler completes, then it has only downloaded artifacts and updated ./MANIFEST.MF — it has not unzipped anything, copied to lib/, or modified run.bat.
AC3 — Restart prompt timing
Given downloads are in progress, then the operator is prompted to restart only after all .artifacts/ downloads complete; the application never auto-restarts.
AC4 — Single entry point
Given the application starts, then _launcher.jar's Initialization class is the sole entry point and registration-client.jar has no main() entry point.
AC5 — Signature verification on startup
Given startup, when _launcher.jar runs, then it verifies the ./MANIFEST.MF signature using the embedded public key before any upgrade decision.
AC6 — Version comparison & path selection
Given a valid signature, when _launcher.jar compares ./MANIFEST.MF vs lib/MANIFEST.MF, then: if versions match it proceeds to normal startup; if they differ it triggers the correct upgrade path.
AC7 — Lib-only patch upgrade path
Given versions differ and the JRE is the supported runtime, then _launcher.jar downloads and verifies lib/MANIFEST.MF (+ .sig) to .TEMP/, resumable-downloads lib.zip to .TEMP/lib.zip, verifies its hash against lib/MANIFEST.MF, unzips to .TEMP/, and prompts restart.
AC8 — Applying staged updates
Given staged files in .TEMP/, when the operator restarts via run.bat, then run.bat copies .TEMP/* → lib/, versions now match, and normal startup proceeds with ClientPreLoader verifying hashes against lib/MANIFEST.MF.
AC9 — Dual-manifest contract
Given the upgrade model, then root ./MANIFEST.MF is used for version tracking/orchestration and contains no lib.zip entry, while lib/MANIFEST.MF is delivered inside lib.zip and is used by ClientPreLoader for per-file hash verification.
AC10 — On-demand lib.zip
Given a version difference is detected, then lib.zip is always downloaded on-demand; nothing is downloaded directly to lib/ from 1.3.0 onwards.
AC11 — Case A (valid manifest sig, file hash fails)
Given ./MANIFEST.MF.sig (or lib/MANIFEST.MF.sig) is valid but a file hash fails, then the file is re-downloaded to .TEMP/, its hash re-verified, the operator is informed to restart, and run.bat (for lib/ files) or _launcher.jar (for root files) places it on restart.
AC12 — Case B (invalid manifest sig)
Given the manifest signature is invalid, then a security alert is shown, nothing is re-downloaded, and the JVM exits.
AC13 — Case C (missing manifest sig)
Given MANIFEST.MF.sig is missing, then it is downloaded from the upgrade server and its integrity checked; if verification fails, Case B handling applies.
AC14 — Resumable downloads
Given any upgrade download (artifacts, manifests, or lib.zip) is interrupted or loses network, when the operator restarts or reconnects, then the download resumes from the last byte rather than restarting.
AC15 — Post-upgrade cleanup
Given versions match on startup after a completed upgrade, then _launcher.jar removes leftover upgrade artefacts (e.g. .artifacts/ and other migration artefacts) before proceeding to normal startup.
AC16 — Server / build pipeline
Given the build pipeline, then configure.sh signs ./MANIFEST.MF and lib/MANIFEST.MF (producing .sig files), the server ./MANIFEST.MF excludes lib.zip, lib/MANIFEST.MF is bundled inside lib.zip, and all files under lib/** are zipped as lib.zip and hosted on the upgrade server.
AC17 — Headless safety
Given a headless environment, when an operator dialog would be shown, then _launcher.jar guards with GraphicsEnvironment.isHeadless() and degrades gracefully instead of throwing.
Sub-Tasks