An audit of what actually reaches customers turned up three things. None is copyleft — there
is no GPL or AGPL anywhere in the product — but two of them are the kind of detail that
undermines a claim to track licences at all.
org.json WAS BEING PACKAGED INTO THE APK. socket.io-client pulls org.json:json:20090211
transitively, and it was landing in the dex in full: 19 classes, including CDL, XML, JSONML
and the library's own Test class. That release carries the JSON Licence, whose "shall be used
for Good, not Evil" clause is not OSI-approved, is treated as non-free by Debian and Fedora,
and is Category X at Apache. Excluded now, and nothing is lost: Android has provided org.json
in the platform since API 1 and minSdk is 24.
Verified rather than assumed. The whole surface used — by socket.io/engine.io and by our own
Kotlin — is JSONObject, JSONArray and JSONTokener, via get/getString/getLong/getJSONArray/
getJSONObject/has/keys/length/isNull/put/NULL, the opt* family, and JSONTokener.nextValue.
Every one is platform API. The rebuilt APK defines 0 org.json classes (was 19) while still
referencing all three, so they now resolve against the platform; it is 25KB smaller and the
v1 JAR signature is intact. Then it was installed on a real panel, which registered over the
socket, paired, and parsed a playlist with no NoSuchMethodError — the failure mode that would
only ever appear at runtime.
REDOC SHIPPED WITH NO LICENCE NOTICE. frontend/vendor/redoc.standalone.js is in the release
tarball, minified with every header stripped, and the vendor README recorded version and
source but not licence. MIT requires the notice to travel with the software. Added as
redoc.LICENSE, with a note in the README that anything vendored here ships and therefore
needs one.
The server manifest declared no licence at all, though the repo is MIT — tooling and auditors
read package.json, not just the root LICENSE. Set, and the lockfile synced so `npm ci` cannot
disagree.
Audited against production's own installed tree rather than a developer checkout: 365
packages, `COPYLEFT — none`.
1676/1676 server tests, Android build + unit tests, `npm ci` clean.
ScheduleEval uses java.time — Instant, LocalDate, ZoneId — which is API 26. minSdk is 24, and core
library desugaring was never enabled, so on Android 7.0/7.1 the first evaluation threw
NoClassDefFoundError. Those API levels are still common on cheap signage sticks and older TV boxes.
The damage was much worse than a failed check, because NoClassDefFoundError is an Error, not an
Exception. The evaluator's deliberate fail-open guard — written so that "a blank screen is worse
than an over-running promo" — did not catch it. The Error propagated out of scheduleAllows, through
firstActiveIndex and updatePlaylist, past another catch(Exception), and was only swallowed at the
service boundary. Because updatePlaylist aborted before the download block, no content was fetched
either; and on a cold start from cache the same Error reached a handler that clears the playlist
cache. So the moment anyone used dayparting or expiry, those panels sat on "waiting for content"
with nothing downloaded and nothing cached, and a reboot did not help. The stated contract was
inverted on exactly the hardware it was meant to protect.
Two changes. Desugaring is the real fix: java.time now exists on API 24/25, so the code runs as
written. The guard is widened to Throwable as well, so this class of failure can never again slip
past a catch that was written to be total — that is belt and braces, not the fix.
Release build assembles cleanly with desugaring on; 134 Android JVM tests green. Still to confirm on
a real API 24/25 image before release — the unit tests run on the JVM, where java.time always
exists, which is precisely why this was invisible to them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
Three prereleases were cut for #234 and handed to the reporter, consuming versionCodes
89 through 93 via VERSION_CODE overrides that were never written back to this file. The
committed default was still 88, so bump-version.sh would have produced 89 for 1.9.25 —
an APK that installs over nothing anyone has been testing, since Android refuses a
lower-or-equal code, and silently so from the user's side.
Set to 93 so the next bump lands on 94, above every published build.
Lesson worth keeping: a VERSION_CODE override for a one-off build leaves this file lying
about where the release line actually is.