From 5b486edff4fccf8a8e55cdef8ef95f84b75d9e29 Mon Sep 17 00:00:00 2001 From: Dom <168790843+firestar5683@users.noreply.github.com> Date: Tue, 27 Oct 2026 03:22:05 -0500 Subject: [PATCH] Declare exact pandad build outputs for CI provenance --- prebuilt.json | 2 +- tools/ci/lifecycle_build_outputs.json | 28 ++++++++++++ .../ci/tests/test_lifecycle_build_identity.py | 45 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/prebuilt.json b/prebuilt.json index a854b8f42f..28455e576f 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "7020ecdca9d99e6ee5476e5f64d7b576a42a5de14149589f4c124be72e1f1464", "schema_version": 1} +{"protected_inventory_sha256": "58099258b1d0dbb928397b80060635f6cc62cbcaa571ed0ae3c760d518df4a12", "schema_version": 1} diff --git a/tools/ci/lifecycle_build_outputs.json b/tools/ci/lifecycle_build_outputs.json index 7823e997de..3a14b29098 100644 --- a/tools/ci/lifecycle_build_outputs.json +++ b/tools/ci/lifecycle_build_outputs.json @@ -119,10 +119,38 @@ "recipe": "openpilot/selfdrive/pandad/SConscript", "required_tracked": true }, + "openpilot/selfdrive/pandad/main.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, + "openpilot/selfdrive/pandad/panda.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, + "openpilot/selfdrive/pandad/panda_safety.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, + "openpilot/selfdrive/pandad/panda_usb.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, "openpilot/selfdrive/pandad/pandad": { "recipe": "openpilot/selfdrive/pandad/SConscript", "required_tracked": true }, + "openpilot/selfdrive/pandad/pandad.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, + "openpilot/selfdrive/pandad/spi.o": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": true + }, + "openpilot/selfdrive/pandad/tests/test_panda_debug": { + "recipe": "openpilot/selfdrive/pandad/SConscript", + "required_tracked": false + }, "openpilot/selfdrive/pandad/tests/test_panda_owner": { "recipe": "openpilot/selfdrive/pandad/SConscript", "required_tracked": false diff --git a/tools/ci/tests/test_lifecycle_build_identity.py b/tools/ci/tests/test_lifecycle_build_identity.py index 7d45e132de..c888c336a0 100644 --- a/tools/ci/tests/test_lifecycle_build_identity.py +++ b/tools/ci/tests/test_lifecycle_build_identity.py @@ -97,6 +97,51 @@ class BuildIdentityTests(unittest.TestCase): with self.assertRaises(ValueError): helper.same_build_inputs(built, forged) + def test_pandad_exact_outputs_preserve_checkout_build_identity(self): + recipe = 'openpilot/selfdrive/pandad/SConscript' + objects = [f'openpilot/selfdrive/pandad/{name}.o' for name in ('main', 'panda', 'panda_safety', 'panda_usb', 'pandad', 'spi')] + executable = 'openpilot/selfdrive/pandad/tests/test_panda_debug' + for name in [*objects, executable]: + self.assertEqual(self.inventory.get(name), {'recipe': recipe, 'required_tracked': name != executable}) + source = self.root / (executable + '.cc') + source.write_text('native test source\n') + subprocess.run([*GIT, 'rm', '--cached', executable], cwd=self.root, check=True, capture_output=True) + subprocess.run([*GIT, 'add', executable + '.cc'], cwd=self.root, check=True) + subprocess.run([*GIT, '-c', 'user.name=Test', '-c', 'user.email=test@example.com', 'commit', '-qm', 'Pandad source fixture'], + cwd=self.root, check=True) + (self.root / executable).unlink() + checkout = helper.build_identity(self.root) + for name in [*objects, executable]: + (self.root / name).write_bytes(b'independent host build') + (self.root / executable).chmod(0o755) + built = helper.build_identity(self.root) + self.assertTrue(helper.same_build_inputs(checkout, built)) + self.assertNotEqual(checkout, built) # In-job output mutation still fails the before/after guard. + self.assertIn(executable + '.cc', built['inputs']['tracked_inputs']) + self.assertIn(recipe, built['inputs']['recipes']) + for name in [*objects, executable]: + self.assertEqual(built['declared_outputs'][name], helper.file_record(self.root / name)) + forged = copy.deepcopy(built) + del forged['declared_outputs'][name] + with self.assertRaises(ValueError): + helper.same_build_inputs(checkout, forged) + for path in (source, self.root / recipe): + original = path.read_bytes() + path.write_bytes(b'changed input') + self.assertFalse(helper.same_build_inputs(built, helper.build_identity(self.root))) + path.write_bytes(original) + neighbor = self.root / 'openpilot/selfdrive/pandad/undeclared.o' + neighbor.write_bytes(b'undeclared object') + self.assertFalse(helper.same_build_inputs(built, helper.build_identity(self.root))) + neighbor.unlink() + for name in objects: + path = self.root / name + original = path.read_bytes() + path.unlink() + with self.assertRaises(ValueError): + helper.build_identity(self.root) + path.write_bytes(original) + def test_missing_declared_artifact_is_denied(self): (self.root / 'msgq_repo/msgq/ipc_pyx.so').unlink() with self.assertRaises(ValueError):