4.5 KiB
ITWorx ModelForge v1.2.1
A packaging and provenance patch over v1.2.0. No new capability, no UX change, no API contract
change, no schema change and no runtime-behaviour change. Schema stays 20260828_0022 and no
migration runs.
v1.2.0 is functionally healthy and remains historical truth, including the fact that its production acceptance found two defects after publication. v1.2.1 fixes them forward rather than reissuing v1.2.0.
Both defects share a shape worth naming: the source was correct and the artifact was wrong. No amount of unit testing, linting or label inspection could see either, because both only exist once an image has been built.
Fixed
The published console could not reach its own API
Vite inlines VITE_API_BASE_URL into the bundle at build time. scripts/release_build.py never
passed it, so every release image since v1.1.0 compiled the Dockerfile's development default —
http://localhost:8000 — into an immutable artifact. The nginx CSP is derived from the same build
argument, so connect-src hardcoded the same wrong origin: the console asked the wrong address, and
its own policy forbade the right one.
In production this presented as a console that loaded perfectly and displayed nothing, with CORS errors visible only in a browser devtools console.
A release build now takes the origin as an explicit input and refuses to package without it:
python scripts/release_build.py --output dist \
--public-api-origin https://modelforge.example.com
# or: MODELFORGE_PUBLIC_API_ORIGIN=https://modelforge.example.com
The value must be an absolute http(s) origin with no path, query or fragment. A missing or
malformed origin fails the build before any image is built.
The Node Agent's identity could drift from its tag
docker-compose.node-agent.yml declared a build: block with no args:. A Compose-built agent
therefore fell back to version 0.0.0 with empty revision and created labels, while still being
tagged from whatever MODELFORGE_VERSION happened to be. Production ran an image tagged 1.1.1
whose contents reported 1.2.0 and whose OCI provenance was blank.
The projection now passes MODELFORGE_VERSION, MODELFORGE_COMMIT and MODELFORGE_BUILT_AT,
exactly as the API and console projections already did. The Node Agent's certified behaviour is
unchanged: same Debian/glibc base, NVML preflight, fail-closed NVIDIA validation, non-root runtime,
cap_drop: ALL, no-new-privileges, protocol 1 and persisted-identity semantics.
Added
scripts/release_image_acceptance.py reads the built console image rather than the source that
produced it: the origins compiled into its JavaScript, the connect-src in its rendered nginx
policy, and its OCI labels. The release build runs it before packaging and refuses to publish an
image that cannot reach the API it was built for.
It was validated against the exact image that broke production — modelforge-web:1.1.0 fails three
of its checks — and against the corrected image, which passes all thirteen.
Regression coverage was added for both defects, including proof that each new test fails when its fix is reverted.
Compatibility
| Contract | v1.2.1 |
|---|---|
| Version | 1.2.1 |
| Channel | stable |
| Database schema | 20260828_0022 — unchanged, no migration |
| Agent protocol | 1, accepts 1 |
| Minimum direct upgrade | v1.0.0 |
| Minimum PostgreSQL major | 16 |
Upgrade
Application-only from any v1.2.x. Replace the API, console and Node Agent images with the published
v1.2.1 artifacts. Schema is 20260828_0022 before and after.
Operators upgrading from v1.1.0 or the original v1.2.0 images should note that the console image is the one that actually changes behaviour here: the previous images cannot reach a non-localhost API, regardless of how the deployment is configured, because the origin is compiled in.
Known limitations
- The console's API origin remains a build-time property. A single published console image is therefore bound to one API origin; deployments behind a different origin need an image built for it. Making this a runtime property is a larger change than a patch release should carry.
- Node Agent HIGH/CRITICAL findings from its digest-pinned Debian base are unchanged from v1.1.1 and v1.2.0, and none has an upstream fix available.
- Unmanaged GPU workloads can legitimately cause
EXTERNAL_GPU_PRESSURE; ModelForge observes but never reclaims them. - Point-in-time database recovery remains
NOT_SUPPORTED.