The checked-in Windows Embedded MPV runtime pin pointed at an upstream release
whose 30-day retention had expired, so `Build on windows x64` failed
repository-wide the moment a runner's cache went cold:
Unable to download Windows embedded MPV runtime archive: 404 Not Found
The weekly refresh exists to rotate the pin well before that boundary, and it
had been red since 2026-09-07 — because its own validation step could only
pass while the pin was stale. `createPinFixture()` copied the CHECKED-IN pin
into the temp dir, and the age-threshold test then asserted, against a clock
frozen at 2026-09-05, that this pin was at least 14 days old. So every
successful rotation invalidated the assertion that guarded it: the step went
red, the bot pull request was never opened, and the pin was left to expire.
Build the refresh fixtures from the synthetic release fixture at an age the
test chooses (`createPinFixture({ ageDays })`) instead. The three refresh tests
are about the rule, so the rule is now what they exercise — one day under the
threshold is left alone, exactly at the threshold rotates, and the unavailable
case is deliberately young so only the 404 can explain it — and the outcome no
longer depends on production data that this job exists to replace.
`CURRENT_PIN` stays with the schema, naming and licence-statement checks, which
hold for any pin. A new case pins the invariant the job actually needs: a pin
written moments ago must read as current on the next run.
Rotate the pin to the newest upstream LGPL x86_64 release. Verified
end-to-end: the archive downloads (27 MB) and its SHA-256 matches the pinned
digest. The binary stays on its upstream host; IPTVnator does not mirror it and
the limited checksum/layout-only licence statement is preserved verbatim.
Also drops three catalog titles from a pending TMDB release note: notes are
published verbatim into CHANGELOG.md, the GitHub release body and the
announcement drafts, so the example now names the letter rather than the shows.
No release note: CI plumbing under `tools/`, outside the gate's `apps/`+`libs/`
trigger, with no user-visible behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The publish-snap verifier had never run against a real release and
encoded three stale expectations that the tag build's own validators do
not share:
- it required the app under usr/lib/iptvnator inside the snap, while
Electron Builder's snap target ships the app at the snap root
(/iptvnator.bin, /resources/**) — the layout the packaged smoke tests
exercise;
- it validated the source archive's runtime manifest with the raw
source-build validator, but the archive carries the STAGED manifest
(origin "vendored-lgpl" + sourceBuildOrigin) written by
stage-runtime.mjs; the staged envelope is now checked explicitly and
the remaining fields still go through the shared validator via an
origin projection;
- it deep-equaled the snap's bundled sourceRuntime against the archive
manifest, but the snap bundles the builder view (no staging
envelope); the binding now projects the envelope away first.
Verified end-to-end in a Linux container against the real v0.23.0
release assets: release-snap-assets.cjs verify now passes and emits the
sealed snapshot receipt. Regression tests cover the legacy usr/lib
layout and staged-envelope mismatches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The "Build pinned Linux Embedded MPV runtime" job failed twice on 2026-08-11
because www.freedesktop.org answered GitHub runners with HTTP 418 for the
fontconfig tarball. The Linux builder curled a single pinned URL with no
fallback, so upstream rate-limiting reddened the build.
Route downloadArchive() through the shared downloadPinnedSource() helper the
macOS builder already uses, and pin a mirror for each single-host source:
fontconfig and libdisplay-info (freedesktop-hosted) plus freetype, which the
macOS builder already mirrors. Each mirror was downloaded and verified to hash
to the existing pin. The curl hardening flags and assertArchiveMatchesPin are
unchanged, and the helper verifies every candidate against the same SHA-256,
so a mirror serving different bytes is rejected rather than used.
Unlike macOS, the Linux manifest keeps sourceUrl at the canonical pinned value
even when a mirror served the bytes: notice generation and the Snap publication
boundary compare that field against the immutable pin. A used mirror is logged
instead.
build-linux-runtime.mjs now imports the downloader, so download-pinned-source.mjs
joins the released source-archive tooling set (otherwise the archive would ship
a build script it cannot run) and the Linux runtime cache key.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The packaging verifier bounded `iptvnator_mpv_helper --runtime-probe` with
RUNTIME_PROBE_TIMEOUT_MS (3s) — a constant it shares with the application's own
startup capability gate. Three seconds is a tight budget for a helper that
dlopens libmpv plus EGL/GL/GBM, and the Flatpak profile is closest to that edge
because the helper runs inside the sandbox against its bundled closure: on
#1277 the job failed three consecutive reruns and passed on the fourth with no
code change, while the concurrent master job passed.
Give the verifier its own budget rather than raising the shared one. The app's
probe is a blocking spawnSync on the Electron main process, so a hung helper
must not stall window creation, and a timeout there degrades gracefully to the
native-view fallback. Nothing waits on the packaging probe but the CI job,
which already has its own 120-minute bound, while a premature kill reports a
healthy package as broken.
- PACKAGE_VERIFICATION_PROBE_TIMEOUT_MS (15s) and
PACKAGE_VERIFICATION_PROBE_MAX_ATTEMPTS (2) join the frozen probe contract;
RUNTIME_PROBE_TIMEOUT_MS stays at 3s for the application gate.
- runBoundedRuntimeProbe() retries only on ETIMEDOUT, repeating the identical
bounded launch (same command, args, env, maxBuffer, killSignal) and
announcing the retry on stderr so a degrading trend stays visible.
Fail-closed behaviour is unchanged. A hard timeout is the one probe outcome
that says nothing about the payload; spawn errors (a missing helper, a wrapper
launched instead of the real ELF), termination by signal, nonzero exits and
malformed or wrong-protocol lines all still fail on the first attempt, and a
helper that keeps hanging still fails once both attempts are spent.
The four new/extended verifier tests cover retry-then-success (asserting the
second launch is identical to the first), exhausted timeouts still rejecting,
four non-timeout verdicts each probing exactly once, and the attempt bound
itself. Setting MAX_ATTEMPTS to 1 fails four of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Port the embedded mpv frame-copy pipeline to Windows with WGL rendering and named shared memory. Includes packaging validation, platform gates, tests, and architecture documentation.
* feat(embedded-mpv): Linux frame-copy helper via headless EGL
Port the frame-copy engine's native layer to Linux (PORTING.md items 1-4):
- frame_helper_gl.h: platform GlContext abstraction. macOS keeps the CGL
path (moved verbatim); Linux acquires an EGL display in order
surfaceless-Mesa -> default display -> GBM render node, binds a 3.2 core
desktop-GL context surfaceless (1x1 pbuffer fallback), and hands mpv
eglGetProcAddress. The helper's own GL calls link against glvnd
libOpenGL, so no display server is required.
- frame_shm.h: portable frame_shm_now_ns() (CLOCK_MONOTONIC) shared by the
helper and the reader addon, replacing the macOS-only
clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); producer and consumer stay on
the same clock.
- embedded_mpv_frame_reader.c: real implementation now also on __linux__
(the code was already POSIX apart from the clock call).
- binding.gyp: OS==linux executable branch for iptvnator_mpv_helper linking
system libmpv (-lmpv) + EGL/OpenGL/gbm, with rpaths for $ORIGIN/lib and
the build-time library dir. The in-process addon still does not link
libmpv - the ban only binds in-process, the helper is out of process.
- build-embedded-mpv.js: system-dev fallback on Linux (LIBMPV_INCLUDE_DIR
or /usr/include) so a distro libmpv-dev install builds without staging a
vendored runtime; a pre-set LINUX_NATIVE_LIBRARY_DIR now wins over the
vendored lib dir.
Verified on Ubuntu 25.04 / i7-1165G7 (Iris Xe): lavfi smoke per PORTING.md
(idle->loading->playing snapshots at 4 Hz, aspect-fit generation bump
g1 1280x720 -> g2 960x720 for a 4:3 source), reader probe 60 fps at
1080p60 with 0 torn reads, clean quit with no leaked processes or shm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(embedded-mpv): enable the frame-copy engine gates on Linux
Flip the TypeScript side of the Linux port (PORTING.md item 5). A shared
dependency-free predicate, isFrameCopyPlatformSupported() (linux any-arch,
darwin arm64-only), now backs all four gates so they cannot drift:
- main.ts: the persisted Settings toggle promotes to the env flag on Linux
too (this runs before window creation and controls the sandbox relax).
- EmbeddedMpvNativeService.isFrameCopyEngineActive/isFrameCopyAvailable.
- EmbeddedMpvFrameCopyAdapter.isSupported.
getSupport() ordering: the frame-copy branch moves above the Linux-only
native-engine prerequisites - the X11/Xwayland display-server check and
the system-mpv-on-PATH probe only bind the --wid native engine, while the
frame-copy helper renders offscreen (headless EGL) and links libmpv
itself. createSession() also skips resolving the native window handle for
frame-copy sessions, which the adapter ignores anyway, so native-Wayland
sessions no longer trip the window-handle assertion.
Settings copy: the i18n frame-copy description now says macOS (Apple
Silicon) and Linux in all 18 languages; stale macOS-only doc comments in
the settings/support interfaces updated alongside.
Tests: platform-gate matrix for the adapter (darwin arm64/x64, linux
x64/arm64, win32) and service specs covering Linux activation under
native Wayland, macOS arm64 staying active, macOS x64 staying native, and
the skipped window handle for frame-copy sessions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(packaging): CI + package guards for the Linux frame-copy helper
- build-and-make.yaml: install libegl-dev/libgl-dev/libgbm-dev on the
Linux runner (the helper's EGL backend needs them now that the helper
target builds on Linux), and verify the built helper exists and DOES
link libmpv - the inverse of the addon's no-libmpv rule, which still
holds and stays validated.
- electron-after-pack.cjs: strip iptvnator_mpv_helper from packaged Linux
apps. It links the build host's system libmpv, which end-user systems
cannot be assumed to have; the support probe treats the missing helper
as frame-copy-unavailable (dev-build-only engine until the
bundled-runtime staging milestone).
- frame_helper_gl.h: log the chosen EGL display tier to stderr (the
adapter mirrors helper stderr), so bring-up problems on exotic setups
are diagnosable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(embedded-mpv): document the Linux frame-copy port
- architecture doc: frame-copy section covers Linux (EGL display tiers,
build deps, package strip), Linux support matrix notes the frame-copy
exception to the X11 + system-mpv requirements, Linux measured baseline.
- RESULTS.md: Ubuntu 25.04 / i7-1165G7 (Iris Xe) measurement rows via the
production helper + reader probe; viewport-size claim reproduced.
- PORTING.md: Linux marked done with pointers to what changed; Windows
remains the open port and its perf gate the open decision.
- CLAUDE.md + tools/embedded-mpv/README.md: platform scope, Linux dev
build requirements, system-headers fallback, helper strip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(embedded-mpv): commit the Linux frame-copy measurement probe
linux-frame-probe.mjs reproduces the RESULTS.md Linux rows: spawns the
production helper, attaches the frame-reader addon to the announced shm
generation, and reports new-frame fps, copy wall time, produce->copy age,
torn reads and pixel spread. Usage documented in RESULTS.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(embedded-mpv): address multi-agent review findings on the Linux port
Confirmed findings (each verified by 3 adversarial reviewers):
- CI would fail to link the helper: -lOpenGL needs the unversioned glvnd
libOpenGL.so, shipped only by libopengl-dev, which neither the runner
images nor the previous apt line provide. Added to the workflow and to
every documented Linux build-dep list.
- The new 'test -x' dist guard could never pass: webpack's dist asset
copy drops file modes (helper arrives as 0644). The guard is now
'test -f'; electron-after-pack.cjs restores the execute bit on packaged
helpers (also fixes packaged-macOS spawns); the support probe now
requires X_OK, so a mode-stripped helper reads as frame-copy-unavailable
and falls back to native instead of failing spawn with EACCES.
- The Settings frame-copy toggle was unreachable in exactly the Linux
states the port targets: the native-Wayland and missing-system-mpv
unsupported payloads omitted frameCopyAvailable, and toggle visibility
derives solely from it. Both returns now advertise availability.
Also from review:
- build-embedded-mpv.js keeps the old graceful-skip contract when the new
system-dev fallback finds libmpv-dev but the GL/EGL/gbm dev stack is
missing (previously such machines skipped; a hard electron-build
failure was a regression).
- createSession derives the window-handle skip from the dispatched addon
instead of re-evaluating the engine gate, so the two cannot disagree.
- The render thread logs the GL renderer string (surfaceless Mesa can
silently pick llvmpipe on non-Mesa-primary systems; now diagnosable —
verified 'Mesa Intel Iris Xe' on this machine).
- Specs pin the new semantics: frameCopyAvailable advertised while native
is unsupported (Wayland / no mpv), frame-copy supported without a
system mpv, and the handle-skip test disposes its session through the
owning adapter.
- Docs: PORTING.md file map reflects the frame_helper_gl.h seam for the
Windows porter; helper-strip removal correctly gated on milestone 4
(bundled libmpv), not milestone 3; RESULTS.md preamble notes the
RAW->MONOTONIC clock change; stale '(macOS)' scope comments updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(embedded-mpv): address Greptile/Codex review comments
- Sandbox gate requires a usable helper (Greptile P1, security): the
main.ts env promotion now also probes for an executable
iptvnator_mpv_helper before relaxing the window sandbox — a stale
opt-in on packaged Linux (helper deliberately stripped) or after a
cleaned native build no longer costs a sandboxless launch for an
engine that cannot activate. Helper discovery (addon candidate paths +
X_OK probe) moved into embedded-mpv-frame-copy-platform.util.ts,
shared by main.ts and the service; the service keeps thin instance
wrappers so tests can stub per scenario. New util spec pins the
platform matrix, candidate resolution, and the execute-bit semantics.
- Stale frame-copy artifacts on skipped builds (Codex P2): cleanOutput()
now also removes iptvnator_mpv_helper and
embedded_mpv_frame_reader.node, so a failed/skipped rebuild cannot
leave a previous helper advertising frame-copy support against a
runtime the build just declared unavailable.
- Multiarch default lib dir (Greptile P1, partially refuted): -l
resolution never depended on our -L (the compiler's built-in search
paths include the Debian/Ubuntu multiarch dir — proven by the green CI
run linking with a nonexistent -L dir), but the system-dev fallback
now defaults to /usr/lib/<multiarch-triple> when present so the -L
flag and the helper's baked rpath point somewhere real.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(embedded-mpv): harden Linux frame-copy port
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds experimental embedded MPV support for Windows and Linux, expands packaging/runtime validation, and updates CI coverage for the cross-platform native addon.
- Introduced tooling for building and staging the macOS `libmpv` runtime for IPTVnator's embedded MPV player.
- Added `build-macos-runtime.mjs` for building an LGPL-compatible runtime from source.
- Created `stage-macos-runtime.mjs` for staging the built runtime artifacts.
- Implemented validation for the packaged embedded MPV runtime in `electron-after-pack.cjs` and `embedded-mpv-macos.cjs`.
- Updated packaging scripts to ensure the embedded MPV runtime is correctly integrated and validated during the build process.
- Added README files to document the expected layout and usage for the embedded MPV runtime artifacts.
Entire-Checkpoint: c6e522b4276c