diff --git a/.github/workflows/pob-codes-feed-proof.yml b/.github/workflows/pob-codes-feed-proof.yml
new file mode 100644
index 00000000000..aa97ba3a168
--- /dev/null
+++ b/.github/workflows/pob-codes-feed-proof.yml
@@ -0,0 +1,32 @@
+name: PoB Codes feed integration proof
+on:
+ pull_request:
+ branches: [tests-branch, dev]
+ paths:
+ - spec/FetchTestBuilds.py
+ - spec/GenerateBuilds.lua
+ - spec/DiffOutput.lua
+ - spec/TestBuilds/**
+ - tests/test_pob_codes_feed.py
+ - .github/workflows/pob-codes-feed-proof.yml
+ workflow_dispatch:
+permissions:
+ contents: read
+jobs:
+ existing_generator:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ - name: Prove adapter, existing generator, and existing comparator
+ env:
+ POB_FEED_DOCKER_TEST: '1'
+ run: python -m unittest discover -s tests -p test_pob_codes_feed.py -v
+ - name: Validate and decode the live monthly batch (manual only)
+ if: github.event_name == 'workflow_dispatch'
+ run: python spec/FetchTestBuilds.py --output "$RUNNER_TEMP/pob-codes-inputs"
diff --git a/spec/FetchTestBuilds.py b/spec/FetchTestBuilds.py
new file mode 100644
index 00000000000..cdbecaf6454
--- /dev/null
+++ b/spec/FetchTestBuilds.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+"""Decode one PoB Codes batch for the existing GenerateBuilds.lua input path."""
+import argparse
+import base64
+import hashlib
+import json
+from pathlib import Path
+import re
+import tempfile
+import urllib.request
+import zlib
+from xml.etree import ElementTree
+
+FEED = "https://api.pob.codes/test-builds"
+MAX_BATCH, MAX_CODE, MAX_XML = 16 * 1024 * 1024, 150 * 1024, 4 * 1024 * 1024
+
+
+def decode_batch(data):
+ if len(data) > MAX_BATCH:
+ raise ValueError("batch too large")
+ batch = json.loads(data)
+ if batch.get("schemaVersion") != 1 or batch.get("requestedCount") != 100:
+ raise ValueError("unsupported feed schema")
+ builds = batch.get("builds")
+ if not isinstance(builds, list) or not 1 <= len(builds) <= 100 or batch.get("count") != len(builds):
+ raise ValueError("invalid build count")
+ decoded = {}
+ for entry in builds:
+ code, key = entry["code"], entry["sha256"]
+ if not isinstance(code, str) or not 0 < len(code) <= MAX_CODE or not re.fullmatch(r"[A-Za-z0-9_+/=-]+", code):
+ raise ValueError("invalid build code")
+ if hashlib.sha256(code.encode("ascii")).hexdigest() != key or key + ".xml" in decoded:
+ raise ValueError("hash mismatch or duplicate build")
+ packed = base64.b64decode(code + "=" * (-len(code) % 4), altchars=b"-_", validate=True)
+ xml = None
+ for window in (zlib.MAX_WBITS, -zlib.MAX_WBITS):
+ try:
+ stream = zlib.decompressobj(window)
+ xml = stream.decompress(packed, MAX_XML + 1)
+ except zlib.error:
+ continue
+ if len(xml) > MAX_XML or stream.unconsumed_tail or not stream.eof or stream.unused_data:
+ raise ValueError("oversized, incomplete, or trailing compressed data")
+ break
+ if xml is None:
+ raise ValueError("invalid compressed build")
+ text = xml.decode("utf-8")
+ if "\0" in text or re.search(r"'),
+ batch_for(b"x" * (MAX_XML + 1))):
+ with self.subTest(batch=batch.get("schemaVersion")), tempfile.TemporaryDirectory() as tmp:
+ output = Path(tmp) / "inputs"
+ with self.assertRaises((ValueError, ElementTree.ParseError)):
+ write_inputs(json.dumps(batch), output)
+ self.assertFalse(output.exists())
+
+ def test_existing_inputs_are_never_overwritten(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ output = Path(tmp) / "inputs"
+ self.assertEqual(write_inputs(json.dumps(self.batch), output), 1)
+ before = {p.name: p.read_bytes() for p in output.iterdir()}
+ with self.assertRaises(ValueError):
+ write_inputs(json.dumps(self.batch), output)
+ self.assertEqual({p.name: p.read_bytes() for p in output.iterdir()}, before)
+
+ @unittest.skipUnless(os.environ.get("POB_FEED_DOCKER_TEST") == "1", "opt-in existing Docker generator proof")
+ def test_existing_generator_and_comparator(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ tmp = Path(tmp)
+ write_inputs(json.dumps(self.batch), tmp / "inputs")
+ for name in ("first", "second", "bad-output"):
+ (tmp / name).mkdir()
+ command = ["docker", "run", "--rm", "--network", "none",
+ "--mount", "type=bind,source=%s,target=/workdir,readonly" % ROOT,
+ "--mount", "type=bind,source=%s,target=/proof" % tmp,
+ "-w", "/workdir", "-e", "HOME=/tmp"]
+
+ def docker(arguments, environment=()):
+ return subprocess.run(command + list(environment) +
+ ["ghcr.io/paliak/busted-tests:latest"] + arguments, timeout=330,
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
+
+ def generate(inputs, output):
+ return docker(["timeout", "300", "busted", "--lua=luajit", "-r", "generate"],
+ ["-e", "BUILDINPUTDIR=/proof/" + inputs, "-e", "BUILDCACHEPREFIX=/proof/" + output])
+
+ for output in ("first", "second"):
+ result = generate("inputs", output)
+ self.assertEqual(result.returncode, 0, result.stdout)
+ self.assertEqual(len(list((tmp / output).glob("*.build"))), 1)
+ name = next((tmp / "first").glob("*.build")).name
+ args = ["luajit", "spec/DiffOutput.lua", "/proof/first/" + name, "/proof/second/" + name]
+ self.assertEqual(docker(args).returncode, 0)
+ saved = (tmp / "second" / name).read_text()
+ changed, count = re.subn(r'(123456789', saved, count=1)
+ self.assertEqual(count, 1, "generator must save calculated stats")
+ (tmp / "second" / name).write_text(changed)
+ self.assertEqual(docker(args).returncode, 1, "existing comparator must detect changed stats")
+ bad = re.sub(br'targetVersion="[^"]+"', b'targetVersion="unsupported"', self.xml, count=1)
+ write_inputs(json.dumps(batch_for(bad)), tmp / "bad-input")
+ result = generate("bad-input", "bad-output")
+ self.assertNotEqual(result.returncode, 0, result.stdout)
+ self.assertFalse(list((tmp / "bad-output").glob("*.build")))
+
+
+if __name__ == "__main__":
+ unittest.main()