PortixOne Runtime 0.1.0 — internal pilot
Pre-release
Pre-release
⚠️ Internal pilot build — not for commercial distributionApproved for: installs on Nerion and Kubia, controlled tests, clean-machine validation, and
pilots with no active commercial licensing and zero paying Creator customers.Not approved for: commercial Creator distribution, uncontrolled external customers, or
enabling licensing as a payment barrier. Licensing ships inert in this build; it is not an
enforcement boundary yet (see SEC-LIC-001).The point of this build is to learn whether Portix really installs, starts, prints, recovers, and
uninstalls on real machines — not to enforce a licence.
2026-07-16 — Print-path fixes and the licensing layer
The first Runtime installer release. Two of the fixes below were found by printing on real hardware, not by reading code — and both had to land before anything could be published.
Fixed
- Accented text printed as mojibake.
EscposBuilder.text()encoded content as UTF-8, but the printer reads one byte per character against its selected code table, soá é í ó ú,ñ Ñ, and¿ ¡ °each arrived as two bytes and printed as two garbage glyphs. Plain ASCII looked fine, which is why it survived earlier testing — and why Spanish receipts were unusable. The builder now emitsESC t 16(WPC1252) afterINITand encodes aslatin1. Confirmed on a SICAR WL88S. - A disconnected printer silently reported success. With the printer unplugged,
Get-Printerstill reportedPrinterStatus=Normal(the Generic / Text Only driver lies once a USB device is gone), so the pre-flight passed, the job reportedcompleted, and the bytes sat in the spooler and printed late on reconnect.Win32_Printer.WorkOfflineis now joined into detection and mapped toOffline, soPrinterOfflineErroris raised before anything is queued. - Malformed request bodies returned HTTP 500 instead of 400.
- Print jobs are now bounded:
copies≤ 100 andcontent≤ 100,000 characters. Both were unbounded. - Pairing codes are generated with
crypto.randomIntinstead ofMath.random()— a pending code is enough to retrieve its token from the unauthenticatedGET /pairing/status. StorageRepositorywrites are atomic (serialize → temp → fsync → rename, with orphan sweep and a Windows/OneDrive-safe retry). A failed write can no longer destroy the last valid file.
Added
- Licensing layer (runtime side). Offline ES256 license-token verification with a fail-closed keyring (production trusts only the production keyring and refuses to boot if a development key is present), the grace/posture state machine (72h technical grace counted from token expiry; commercial grace kept separate), a best-effort 12h heartbeat that only honors an authenticated revocation over TLS, one-time installation-token exchange, and an admin-only
GET /license. Licensing never gates printing — the print layer doesn't even import it, and an architecture test freezes that. Design and terms: LICENSING_PLAN.md, docs/licensing/. - Pricing settled as Free / Creator ($24/mo or $240/yr) / Founder ($240 one-time, 100 seats).
- A test runner (
node --testviatsx, zero new dependencies). 100 tests.