Declare exact pandad build outputs for CI provenance

This commit is contained in:
Dom
2026-10-27 03:22:05 -05:00
parent 7dc37f190f
commit 5b486edff4
3 changed files with 74 additions and 1 deletions
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "7020ecdca9d99e6ee5476e5f64d7b576a42a5de14149589f4c124be72e1f1464", "schema_version": 1}
{"protected_inventory_sha256": "58099258b1d0dbb928397b80060635f6cc62cbcaa571ed0ae3c760d518df4a12", "schema_version": 1}
+28
View File
@@ -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
@@ -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):