Identify failed resume qualification gates safely

This commit is contained in:
mikecarper committed 2026-10-06 09:29:48 -07:00
1 parent ba6e48b5ce
commit 24a223314c
2 files changed
+96 -4

No files matched your search

+19 -4
View File
@@ -4858,6 +4858,8 @@ build_firmware_one_profile() {
local BUILD_RECIPE_SHA256
local recipe_build_flags
local completed_recipe
local resume_recipe_status
local resume_artifact_status
local platformio_package_lock_fd=""
local -a pio_run_args=()
local -a BUILD_CAPABILITIES=()
@@ -5117,13 +5119,26 @@ build_firmware_one_profile() {
"$full_source_commit") || return 1
if [ "$RESUME_BUILD_OUTPUT" == "1" ]; then
if python3 scripts/firmware_build_recipe.py occupied "${OUTPUT_DIR}/${firmware_filename}"; then
# Keep the recipe-first short circuit while distinguishing its failure
# from the packaged artifact/capability/RAM gate. Report only exit codes;
# build inputs and raw manifests can contain private settings.
resume_artifact_status=not-run
if python3 scripts/firmware_build_recipe.py matches \
"${OUTPUT_DIR}/${firmware_filename}.capabilities.json" "$BUILD_RECIPE_SHA256" \
>/dev/null 2>&1 \
&& build_artifacts_exist "$env_name" "$env_platform" "$firmware_filename"; then
echo "Skipping ${env_name}; matching recipe and qualified artifacts found for ${firmware_filename}."
return 0
>/dev/null 2>&1; then
resume_recipe_status=0
if build_artifacts_exist "$env_name" "$env_platform" "$firmware_filename" \
>/dev/null 2>&1; then
echo "Skipping ${env_name}; matching recipe and qualified artifacts found for ${firmware_filename}."
return 0
else
resume_artifact_status=$?
fi
else
resume_recipe_status=$?
fi
printf 'Resume qualification gates: recipe_exit=%s; artifact_capability_ram_exit=%s.\n' \
"$resume_recipe_status" "$resume_artifact_status" >&2
echo "Cannot resume ${firmware_filename}: existing package has a missing/different recipe or failed qualification." >&2
echo "Existing files were preserved. Use a fresh OUTPUT_DIR or a different --firmware-version." >&2
return 1
+77
View File
@@ -375,6 +375,81 @@ exit "$build_result"
self.assertNotIn("Skipping", result.stdout)
self.assertIn("fresh OUTPUT_DIR", result.stderr)
self.assertEqual(before, self.snapshot())
return result
def test_resume_recipe_rejection_does_not_execute_artifact_gate(self):
self.qualified_fixture()
result = self.assert_conflict(settings=r'''RADIO_FREQ_OVERRIDE=910.525
build_artifacts_exist() { printf 'FORBIDDEN_ARTIFACT_GATE\n' >&2; return 73; }
''')
self.assertIn("Resume qualification gates: recipe_exit=1; artifact_capability_ram_exit=not-run.",
result.stderr)
self.assertNotIn("FORBIDDEN_ARTIFACT_GATE", result.stdout + result.stderr)
self.assertIn("existing package has a missing/different recipe or failed qualification.",
result.stderr)
def test_resume_recipe_error_status_is_private_and_still_fails_closed(self):
self.qualified_fixture()
result = self.assert_conflict(settings=r'''
python3() {
if [ "$1" = scripts/firmware_build_recipe.py ] && [ "$2" = matches ]; then
printf '%s\n' PRIVATE_FIXTURE_SECRET
printf '%s\n' PRIVATE_FIXTURE_SECRET >&2
return 73
fi
command python3 "$@"
}
build_artifacts_exist() { printf 'FORBIDDEN_ARTIFACT_GATE\n' >&2; return 74; }
'''.replace("PRIVATE_FIXTURE_SECRET", SECRET))
self.assertEqual(result.returncode, 1)
self.assertIn("recipe_exit=73; artifact_capability_ram_exit=not-run.", result.stderr)
self.assertNotIn("FORBIDDEN_ARTIFACT_GATE", result.stdout + result.stderr)
def test_resume_corrupt_artifact_and_failed_ram_report_identify_qualification_gate(self):
self.qualified_fixture()
application = Path(str(self.stem) + ".bin")
original_application = application.read_bytes()
original_memory = self.memory.read_bytes()
for failure in ("application", "ram"):
with self.subTest(failure=failure):
application.write_bytes(original_application)
self.memory.write_bytes(original_memory)
if failure == "application":
application.write_bytes(b"tampered packaged application")
else:
manifest = json.loads(original_memory)
manifest["passed"] = False
self.memory.write_text(json.dumps(manifest))
result = self.assert_conflict()
self.assertIn("recipe_exit=0; artifact_capability_ram_exit=1.", result.stderr)
def test_resume_missing_packaged_marker_identifies_artifact_gate_without_exposing_marker(self):
self.qualified_fixture()
# Add a current requirement after the real contract is declared. The
# cached package remains sealed and its recipe still matches, but its
# actual packaged-application evidence cannot satisfy this requirement.
result = self.assert_conflict(settings=r'''
contract=$(declare -f declare_build_capability_contract)
eval "${contract/declare_build_capability_contract /fixture_original_contract }"
declare_build_capability_contract() {
fixture_original_contract "$@"
BUILD_APPLICATION_EXPECTATIONS+=("fixture.private=''' + SECRET + r'''")
}
''')
self.assertIn("recipe_exit=0; artifact_capability_ram_exit=1.", result.stderr)
self.validate_fixture()
def test_resume_artifact_error_status_suppresses_private_gate_output(self):
self.qualified_fixture()
result = self.assert_conflict(settings=r'''
build_artifacts_exist() {
printf '%s\n' PRIVATE_FIXTURE_SECRET
printf '%s\n' PRIVATE_FIXTURE_SECRET >&2
return 74
}
'''.replace("PRIVATE_FIXTURE_SECRET", SECRET))
self.assertEqual(result.returncode, 1)
self.assertIn("recipe_exit=0; artifact_capability_ram_exit=74.", result.stderr)
def test_fresh_build_attaches_recipe_and_exact_qualified_match_skips(self):
self.qualified_fixture()
@@ -382,6 +457,7 @@ exit "$build_result"
result = self.build()
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("matching recipe and qualified artifacts", result.stdout)
self.assertNotIn("Resume qualification gates:", result.stdout + result.stderr)
self.assertNotIn("STUB_BUILD_REQUIRED", result.stdout)
self.assertEqual(before, self.snapshot())
@@ -422,6 +498,7 @@ exit "$build_result"
self.assertEqual(len(attempts), 1)
attempt = attempts[0]
self.assertIn("Cannot resume", attempt.read_text())
self.assertIn("recipe_exit=1; artifact_capability_ram_exit=not-run.", attempt.read_text())
self.assertNotIn("STUB_BUILD_REQUIRED", result.stdout + attempt.read_text())
self.assertNotIn(SECRET, attempt.read_text())
self.assertIn("status 1; log: " + str(attempt), result.stdout)