Files

393 lines
16 KiB
Markdown

# 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`.
## Clean-room legal requirements (CRITICAL)
### 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:
- Wayland protocol spec (https://wayland.freedesktop.org/docs/html/)
- wlroots C source (MIT, https://gitlab.freedesktop.org/wlroots/wlroots)
- Weston C source (MIT, https://gitlab.freedesktop.org/wayland/weston)
- Smithay Rust source (MIT, https://github.com/Smithay/smithay)
- WebGL2/WebCodecs/KCP MDN docs
### Legal-defensibility artifacts
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) |
## CI/CD license hygiene gates (recommended)
Add these to your CI pipeline to catch GPL contamination early:
### Docker image audit
```bash
# Must return zero matches
dpkg -l | grep -E "libx264|libx265|gstreamer1.0-plugins-ugly|gstreamer1.0-libav"
```
### npm license check
```bash
# 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
```bash
# Must pass (no GPL/AGPL)
pip-licenses --fail-on "GPL.*;AGPL.*" --ignore-builtin
```
### Runtime GStreamer plugin check
```bash
# 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.