Files

16 KiB

11 — Pitfalls, GPL Contaminants, Clean-Room Requirements

This is the prominent pitfalls chapter for the MIT approach. It documents:

  1. 3 GPL contaminants found (must be replaced)
  2. Clean-room legal requirements (cannot read AGPL source and reimplement)
  3. Engineering pitfalls of building from scratch
  4. Risk analysis

Critical finding: 3 GPL contaminants

The user's #1 priority is license purity — no GPL/AGPL contamination of the shipping product. The license audit (Agent T) found 3 GPL contaminants that MUST be replaced:

Contaminant 1: @gfld/compositor-ffmpeg-h264 is GPL-3.0, not Apache-2.0

What Greenfield claims: package.json declares "license": "Apache-2.0".

What the LICENSE file actually says: Full GPL v3 text.

Why this happened: The TypeScript wrapper (H264Decoder.ts, H264Worker.ts, index.ts) is genuinely Apache-2.0. But the build_wasm.sh script compiles FFmpeg with --enable-gpl --enable-version3 --enable-nonfree, which makes the resulting WASM binary GPL-3.0. The 1.8 MB compiled libav-h264.js contains embedded GPL/LGPL license strings.

Impact on the user: Using @gfld/compositor-ffmpeg-h264 would force the entire Odoo fork to be GPL-3.0.

Replacement: WebCodecs VideoDecoder API (browser-native, hardware-accelerated, W3C standard). Fallback: OpenH264 BSD Wasm (NOT FFmpeg).

Contaminant 2: gstreamer1.0-plugins-ugly (provides x264enc) is GPL-2.0+

What it provides: The x264enc GStreamer element, which is the software H.264 encoder Greenfield uses by default.

Why it's GPL: libx264.so.164 is GPL-2.0+. The plugin that wraps it (gst-plugins-ugly) inherits the GPL. Loading this plugin into the Python process = GPL contamination under FSF's strict interpretation.

Verified: ldd libgstx264.so → links libx264.so.164 (GPL-2.0+). x264 has NO dynamic-linking exception.

Impact: Using x264enc would force the entire Python proxy to be GPL-2.0+.

Replacement: Use vaapih264enc (Intel/AMD, LGPL plugin + MIT libva), nvh264enc (NVIDIA, BSD plugin + proprietary NVENC blob), or openh264enc (CPU fallback, BSD libopenh264).

Contaminant 3: gstreamer1.0-libav on Debian is GPL (HIDDEN TRAP)

What it provides: The gst-libav plugin set, which wraps FFmpeg's libavcodec for various codecs.

Why it's a trap: The gst-libav plugin SOURCE CODE is LGPL-2.1+. BUT Debian's libavcodec.so.61 is built with --enable-gpl and directly links libx264.so.164 + libx265.so.215.

Verified: ldd libgstlibav.so → libavcodec.so.61 → libx264.so.164

  • libx265.so.215.

Impact: Even if the user might never use avenc_h264, the mere presence of libgstlibav.so triggers GStreamer's plugin auto-discovery, which dlopens it, which loads the GPL'd FFmpeg into the Python process's address space.

Replacement: Don't install gstreamer1.0-libav. Use only gst-plugins-base + gst-plugins-good + gst-vaapi + BSD subset of gst-plugins-bad.

The problem

You CANNOT read Greenfield's AGPL @gfld/compositor source and reimplement it as "inspired-by" code. Even non-literal similarity is sufficient to find derivative-work infringement.

Reasons

  1. AFC test risk: Under Computer Associates v. Altai's Abstraction-Filtration-Comparison test, even non-literal similarity (same class names, method names, code structure) is sufficient to find derivative-work infringement. A single developer who reads AGPL source then writes a TS replacement will almost certainly reproduce the SSO (structure, sequence, organization) subconsciously.

  2. AGPL §13 network-use clause: This is the killer for an Odoo fork. AGPL-3.0 §13 requires offering the source of any modified AGPL work to all users interacting with it over a network. If a court finds the custom compositor is an AGPL derivative, the entire Odoo fork (which links to the compositor) must be open-sourced to all users — destroying the proprietary value.

  3. Westfield doesn't help: Greenfield's predecessor repo at https://github.com/udevbe/westfield IS MIT-licensed, but it contains ONLY the protocol infrastructure (the same @gfld/common + @gfld/compositor-protocol + @gfld/compositor-wasm we already have). It NEVER had the in-browser compositor logic (Surface/Renderer/Seat/etc.). The compositor code was always in the Greenfield monorepo, always AGPL.

Right approach: True clean-room

  1. Person A (spec author, MAY read AGPL source) writes a behavioral spec (inputs/outputs/edge cases, NO code).
  2. Person B (implementer, MUST NOT open packages/compositor/src/) implements from spec + MIT references only:

Maintain these as legal-defensibility artifacts:

  • SPEC.md — the behavioral spec (written by Person A)
  • CLEAN_ROOM_LOG.md — who read AGPL, who didn't, when, what they referenced

What can Person B reference?

Reference License OK to read for implementation?
Greenfield's packages/compositor/src/ AGPL-3.0 ❌ NO (Person B forbidden)
Greenfield's libs/ (MIT) MIT ✅ Yes (already reused)
Greenfield's protocol/ XML MIT ✅ Yes
Greenfield's docs/ MIT ✅ Yes
Wayland protocol spec MIT ✅ Yes
wlroots C source MIT ✅ Yes
Weston C source MIT ✅ Yes
Smithay Rust source MIT ✅ Yes
MDN docs (WebGL2, WebCodecs, etc.) CC-BY-SA ✅ Yes
pywayland source Apache-2.0 ✅ Yes
qtile source GPL-2.0 ❌ NO (architectural patterns OK, code not)

License audit table (full stack)

Browser side

Component License Compatible with proprietary?
@gfld/common MIT ✅
@gfld/client-protocol MIT ✅
@gfld/compositor-protocol MIT ✅ (verified NO @gfld/compositor import)
@gfld/client-generator MIT ✅
@gfld/compositor-generator MIT ✅
@gfld/compositor-wasm (pixman+xkbcommon) MIT ✅
@gfld/xtsb (X11 bindings, optional) MIT ✅ (only if XWayland needed)
@gfld/compositor-ffmpeg-h264 GPL-3.0 ❌ REPLACE with WebCodecs
Custom browser compositor (your code) PROPRIETARY ✅ (clean-room)
WebCodecs API W3C standard ✅
WebGL2 W3C standard ✅
OffscreenCanvas W3C standard ✅
OpenH264 BSD Wasm (fallback) BSD-2-Clause ✅

Python backend

Component License Compatible with proprietary?
pywayland Apache-2.0 ✅
python-xkbcommon (xkbcommon PyPI) MIT ✅
PyGObject (gi.repository.Gst) LGPL-2.1+ ✅ (dynamic linking)
GLib LGPL-2.1+ ✅ (dynamic linking)
GStreamer core LGPL-2.0+ ✅ (dynamic linking)
gst-plugins-base LGPL ✅
gst-plugins-good (pngenc) LGPL ✅
gst-vaapi (vaapih264enc) LGPL + MIT (libva) ✅
gst-plugins-bad (nvh264enc, openh264enc) BSD subset ✅
gst-plugins-ugly (x264enc) GPL-2.0+ ❌ DROP
gst-libav (libavcodec) LGPL source, GPL binary on Debian ❌ DROP
libwayland MIT ✅
libdrm MIT ✅
mesa (libGL, libEGL, libgbm) MIT ✅
aiohttp Apache-2.0 ✅
kcp (PyPI, RealistikDash) MIT ✅
PyJWT MIT ✅
bubblewrap (bwrap) LGPL-2.0+ ✅ (dynamic linking)
libseccomp LGPL-2.1 ✅

Odoo integration

Component License Compatible?
Odoo 20 Community LGPL-3.0 ✅ (separate process, no contamination)
Nginx BSD-2-Clause ✅
Docker Apache-2.0 ✅

Vendored Greenfield C files (MIT, reused verbatim)

File License Reuse?
westfield-buffer.c MIT (Greenfield) ✅
westfield-dmabuf.c MIT (Greenfield) ✅
westfield-egl.c MIT (Greenfield) ✅
westfield-xwayland.c MIT (Greenfield) ✅
wlr_drm.c MIT (vendored from wlroots) ✅
wlr_linux_dmabuf_v1.c MIT (vendored from wlroots) ✅
drm_format_set.c MIT (vendored from wlroots) ✅
addon.c MIT (vendored from wlroots) ✅
changes.diff MIT (Greenfield patches to libwayland) ✅ (optional, for fast-sync)

Add these to your CI pipeline to catch GPL contamination early:

Docker image audit

# Must return zero matches
dpkg -l | grep -E "libx264|libx265|gstreamer1.0-plugins-ugly|gstreamer1.0-libav"

npm license check

# Must pass (no GPL/AGPL)
npx license-checker --production --failOn "GPL-3.0;AGPL-3.0;GPL-2.0;GPL-2.0+;GPL-3.0+"

Python license check

# Must pass (no GPL/AGPL)
pip-licenses --fail-on "GPL.*;AGPL.*" --ignore-builtin

Runtime GStreamer plugin check

# Must return zero matches
gst-inspect-1.0 --print-plugin-auto-install-info | grep -E "x264|x265|libav"

Engineering pitfalls (beyond licensing)

1. Browser compositor is the biggest effort

The custom browser compositor is ~13,400 LOC TypeScript for MVP, ~25 dev-weeks (1 senior dev). This is the critical path.

Mitigation: Hire 2 senior engineers to parallelize. Use Smithay (Rust, MIT) as a reference for Wayland semantics. Use wlroots (C, MIT) as a reference for compositor patterns.

2. xdg-shell state machine is the highest-risk component

The configure/ack-configure round-trip, popup positioner math, and subsurface sync/desync cascades are subtle. Bugs here can cause apps to hang or crash.

Mitigation: Write extensive unit tests using Weston's test clients (ported to TypeScript). Test with real GTK4/Qt6 apps early and often.

3. asyncio + libwayland integration is under-documented

The 5-line cffi shim for wl_event_loop_get_fd is verified working, but the pattern is not widely documented. Bugs here could cause Wayland events to be dropped or duplicated.

Mitigation: Spend 1 day on a standalone prototype before committing. Reference qtile's Wayland backend (architectural patterns OK, code is GPL — don't copy).

4. GStreamer threading is non-trivial

GLib MainLoop runs in a separate thread; results must be marshalled back to asyncio via asyncio.run_coroutine_threadsafe. Bugs here can cause race conditions or deadlocks.

Mitigation: Reuse Greenfield's MIT C files via cffi — only the marshalling needs Python. Test with high-frame-rate content (e.g. glxgears).

5. XWayland is a major time sink (~8 weeks)

If you need X11 app support (e.g. older apps that don't speak Wayland natively), the XWayland bridge is ~5,000 LOC TypeScript + ~465 LOC C. It uses @gfld/xtsb (MIT, REUSED) for X11 protocol bindings.

Mitigation: Skip XWayland in MVP. Most modern GTK4/Qt6 apps are Wayland-native. Add XWayland only if a specific X11-only app is needed.

6. KCP Python port may have performance issues

The kcp PyPI package (RealistikDash, MIT) is a Cython wrapper over upstream ikcp.c. Performance should be adequate, but if it's not, porting Greenfield's TypeScript KCP (~1100 lines) to Python would be ~1500 lines of pure Python (slower).

Mitigation: Benchmark early. If Cython wrapper is too slow, write a C extension via cffi.

7. WebGL2 + OffscreenCanvas + Workers is complex

Multi-threaded rendering with OffscreenCanvas requires careful message passing between workers. Debugging is harder than main-thread code.

Mitigation: Start with main-thread WebGL2 (simpler), then move to OffscreenCanvas in Phase 5 (polish).

8. WebCodecs hardware decode may have bugs

Greenfield explicitly disables hardware decode with // FIXME hardware decoding frame offset is wrong. We're enabling it (prefer-hardware), but the bug Greenfield hit may still affect us.

Mitigation: Test extensively on Intel/AMD/NVIDIA GPUs. Fall back to prefer-software if hardware decode is broken on specific hardware.

9. Bubblewrap + seccomp may break some apps

Some GTK apps may need additional filesystem access (e.g. ~/.config for settings, ~/.cache for thumbnails). The default-deny approach may break them.

Mitigation: Test with the apps you want to support. Add per-app overrides in compositor.app model (e.g. extra filesystem binds).

10. JWT secret rotation

The JWT secret is shared between Odoo and the compositor proxy via env var. If the secret leaks, attackers can forge JWTs and run apps on behalf of any user.

Mitigation: Rotate the secret periodically (e.g. monthly). Use a secrets manager (Vault, AWS Secrets Manager). Log all JWT validations.

Risk analysis summary

Risk Likelihood Impact Mitigation
GPL contamination in shipping product Medium (3 contaminants found) CRITICAL (forces open-source) 3 substitutions + CI/CD license gates
AGPL derivative-work lawsuit Low (clean-room approach) CRITICAL (forces open-source) True clean-room with SPEC.md + CLEAN_ROOM_LOG.md
Browser compositor takes >6 months Medium High (delays MVP) 2 senior engineers in parallel
xdg-shell state machine bugs High Medium (app crashes) Extensive testing with Weston test clients
asyncio + libwayland integration bugs Medium High (events dropped) 1-day prototype before committing
GStreamer threading deadlocks Medium Medium (encoder stalls) Reuse Greenfield's MIT C files via cffi
XWayland scope creep High High (+8 weeks) Skip XWayland in MVP
KCP Python performance Low Medium (latency) Benchmark early, fall back to cffi C extension
WebGL2 + Workers complexity Medium Medium (debugging pain) Start with main-thread, move to Workers in Phase 5
WebCodecs hardware decode bugs Medium Medium (CPU usage) Test on Intel/AMD/NVIDIA, fall back to software
Bubblewrap breaks apps Medium Low (per-app overrides) Test with target apps, add per-app overrides
JWT secret leak Low CRITICAL (auth bypass) Rotate periodically, use secrets manager

Design wins to copy from Greenfield

These Greenfield design choices are worth copying in the MIT approach (but implemented clean-room):

  1. Generator-driven protocol stubs — single XML source for both sides. REUSED from Greenfield's MIT libs.
  2. Dual-buffer commit semantics — Surface state vs pendingState, atomic merge. Clean-room from spec.
  3. UserShellApi decoupling — shell sees only {events, actions}, never Wayland resources. Clean-room from spec.
  4. Asynchronous browser compositor — per-client channels, never blocks on a slow client. Clean-room from spec.
  5. KCP-over-WebSocket for UDP-like latency over TCP. Use kcp PyPI package (MIT).
  6. WebCodecs primary + fallback — capability probe at session startup. Clean-room, but enable hardware decode (Greenfield doesn't).
  7. Zero-copy dmabuf → encoder via linux-dmabuf-unstable-v1. REUSED vendored Greenfield C files (MIT).
  8. Per-surface encoder feedback channel — Greenfield produces it but doesn't use it. Our MIT approach consumes it for adaptive bitrate (NEW).
  9. ReconnectingWebSocket with exponential backoff. Vendored from pladaria (MIT).
  10. Honest docs — Greenfield's docs/pages/design/ openly admits what's wrong. Use as reference for trade-off analysis.

Top-5 recommendations

  1. Replace the 3 GPL contaminants IMMEDIATELY: WebCodecs (not FFmpeg Wasm), vaapih264enc/nvh264enc/openh264enc (not x264enc), don't install gst-libav.
  2. Adopt true clean-room discipline for the browser compositor. One spec author (may read AGPL), one implementer (must NOT). Maintain SPEC.md + CLEAN_ROOM_LOG.md as legal-defensibility artifacts.
  3. Skip XWayland in MVP — add 8 weeks only if legacy X11 apps are required.
  4. Use the kcp PyPI package — don't port Greenfield's TS KCP to Python (would be slower).
  5. Add CI/CD license hygiene gates — Docker image audit, npm license-checker, pip-licenses, gst-inspect check. Catch GPL contamination early.