4"""Offline gate for reproducible third-party patch series.
6Vendored dependencies stay ready to compile: reviewed patches are already
7present in their checked-in bytes. This gate proves, without fetching the
8network, that each declared patch series reverses those bytes to the upstream
9blob recorded in docs/sbom/upstream and reapplies to the checked-in blob.
11Fetched dependencies use the other supported delivery model: the build checks
12out a pin and applies the numbered series. For those, this gate verifies the
13pin, series, and application entry point are connected, AND that the recorded
14upstream record still describes the CURRENT pin: `upstream_pin` must equal the
15live pin value, and every file the series touches must be recorded with a blob
16whose id the patch's own pre-image abbreviation prefixes.
18Honest limits, offline. The gate has no git objects for a fetched dependency,
19so it cannot recompute a blob: it compares the registry against the patch's
20`index` line, and those are two first-party artifacts. What it DOES catch is a
21pin bump that orphans the series, a patch edited away from its recorded
22pre-image, a target with no record, and a record no patch touches. Proving the
23blob ids against the real upstream remains the job of the networked
24`soup-upstream-refresh` gate.
27from __future__
import annotations
37from dataclasses
import dataclass
38from pathlib
import Path, PurePosixPath
39from types
import SimpleNamespace
41sys.path.insert(0, str(Path(__file__).resolve().parent))
42sys.path.insert(0, str(Path(__file__).resolve().parents[1] /
"gen"))
43sys.path.insert(0, str(Path(__file__).resolve().parents[1] /
"dev"))
45from git_environment
import sanitized_git_environment, trusted_git_executable
46from sbom_registry
import PROV_NOT_VENDORED, REGISTRY
47from soup_manifest
import KIND_PATCH, ManifestError, manifest_path, parse_manifest
49REPO_ROOT = Path(__file__).resolve().parents[2]
50POLICY_PATH = Path(
"docs/sbom/patches/registry.toml")
51NUMBERED_PATCH_RE = re.compile(
r"\A[0-9]{4}-[a-z0-9][a-z0-9-]*\.patch\Z")
52HEX40_RE = re.compile(
r"\A[0-9a-f]{40}\Z")
53DELIVERIES = frozenset((
"vendored",
"fetched"))
54CLASSIFICATIONS = frozenset((
"functional",
"metadata"))
55METADATA_NAMES = frozenset((
".gitattributes",
".gitignore",
".gitmodules"))
56NUMSTAT_FIELD_COUNT = 3
59@dataclass(frozen=True)
61 """One numbered transformation and its review classification."""
67@dataclass(frozen=True)
69 """One vendored or fetched component's ordered patch series."""
74 patches: tuple[PatchItem, ...]
75 apply_script: Path |
None =
None
76 series_token: str |
None =
None
77 pin_file: Path |
None =
None
78 pin_key: str |
None =
None
81 upstream_blobs: tuple[tuple[str, str], ...] = ()
82 upstream_pin: str |
None =
None
85def _safe_rel_path(value: object, where: str) -> Path:
86 """Return a validated repository-relative POSIX path."""
87 if not isinstance(value, str)
or not value:
88 msg = f
"{where}: expected a non-empty path string"
90 pure = PurePosixPath(value)
91 if pure.is_absolute()
or ".." in pure.parts
or str(pure) != value:
92 msg = f
"{where}: path must be normalized and repository-relative: {value!r}"
97def _parse_patch(raw: object, where: str) -> PatchItem:
98 """Parse and validate one patch table."""
99 if not isinstance(raw, dict):
100 msg = f
"{where}: patch entry must be a table"
102 unknown = set(raw) - {
"file",
"classification"}
104 msg = f
"{where}: unknown patch fields: {', '.join(sorted(unknown))}"
105 raise ValueError(msg)
106 filename = raw.get(
"file")
107 classification = raw.get(
"classification")
108 if not isinstance(filename, str)
or not NUMBERED_PATCH_RE.fullmatch(filename):
109 msg = f
"{where}: patch file must match NNNN-lower-kebab.patch"
110 raise ValueError(msg)
111 if classification
not in CLASSIFICATIONS:
112 msg = f
"{where}: classification must be functional or metadata"
113 raise ValueError(msg)
114 return PatchItem(filename, classification)
117def _parse_upstream_blobs(raw: object, where: str) -> tuple[tuple[str, str], ...]:
118 """Parse the recorded pin blob table for one fetched component."""
119 if not isinstance(raw, dict)
or not raw:
120 msg = f
"{where}.upstream_blobs: expected a non-empty table"
121 raise ValueError(msg)
122 for name, value
in raw.items():
123 if not isinstance(value, str)
or not HEX40_RE.fullmatch(value):
124 msg = f
"{where}.upstream_blobs.{name}: expected one full 40-hex blob id"
125 raise ValueError(msg)
126 return tuple(sorted(raw.items()))
129def _parse_upstream_record(raw: dict, delivery: object, where: str) -> dict[str, object]:
130 """Parse the optional recorded-upstream fields of a fetched component."""
131 if "upstream_blobs" not in raw
and "upstream_pin" not in raw:
133 if delivery !=
"fetched":
134 msg = f
"{where}: upstream_blobs/upstream_pin apply only to a fetched component"
135 raise ValueError(msg)
136 upstream_pin = raw.get(
"upstream_pin")
137 if not isinstance(upstream_pin, str)
or not HEX40_RE.fullmatch(upstream_pin):
138 msg = f
"{where}.upstream_pin: expected one full 40-hex commit id"
139 raise ValueError(msg)
141 "upstream_blobs": _parse_upstream_blobs(raw.get(
"upstream_blobs"), where),
142 "upstream_pin": upstream_pin,
146def _parse_component(raw: object, index: int) -> PatchComponent:
147 """Parse and validate one component table."""
148 where = f
"component[{index}]"
149 if not isinstance(raw, dict):
150 msg = f
"{where}: entry must be a table"
164 unknown = set(raw) - allowed
166 msg = f
"{where}: unknown fields: {', '.join(sorted(unknown))}"
167 raise ValueError(msg)
169 delivery = raw.get(
"delivery")
170 if not isinstance(key, str)
or not key:
171 msg = f
"{where}: key must be a non-empty string"
172 raise ValueError(msg)
173 if delivery
not in DELIVERIES:
174 msg = f
"{where}: delivery must be vendored or fetched"
175 raise ValueError(msg)
176 patches_raw = raw.get(
"patches")
177 if not isinstance(patches_raw, list)
or not patches_raw:
178 msg = f
"{where}: at least one [[component.patches]] entry is required"
179 raise ValueError(msg)
181 _parse_patch(item, f
"{where}.patches[{i}]")
for i, item
in enumerate(patches_raw)
183 filenames = tuple(item.file
for item
in patches)
184 if len(set(filenames)) != len(filenames):
185 msg = f
"{where}: duplicate patch filename"
186 raise ValueError(msg)
187 kwargs: dict[str, object] = {}
188 for field
in (
"apply_script",
"pin_file"):
190 kwargs[field] = _safe_rel_path(raw[field], f
"{where}.{field}")
191 for field
in (
"series_token",
"pin_key"):
194 if not isinstance(value, str)
or not value:
195 msg = f
"{where}.{field}: expected a non-empty string"
196 raise ValueError(msg)
197 kwargs[field] = value
198 kwargs.update(_parse_upstream_record(raw, delivery, where))
199 return PatchComponent(
202 _safe_rel_path(raw.get(
"series"), f
"{where}.series"),
208def load_policy(root: Path) -> tuple[PatchComponent, ...]:
209 """Load the strict machine-readable patch registry."""
210 path = root / POLICY_PATH
212 raw = tomllib.loads(path.read_text(encoding=
"utf-8"))
213 except (OSError, tomllib.TOMLDecodeError)
as exc:
214 msg = f
"{POLICY_PATH}: {exc}"
215 raise ValueError(msg)
from exc
216 if set(raw) != {
"schema_version",
"component"}
or raw.get(
"schema_version") != 1:
217 msg = f
"{POLICY_PATH}: expected only schema_version=1 and component tables"
218 raise ValueError(msg)
219 rows = raw.get(
"component")
220 if not isinstance(rows, list)
or not rows:
221 msg = f
"{POLICY_PATH}: no component tables"
222 raise ValueError(msg)
223 components = tuple(_parse_component(row, index)
for index, row
in enumerate(rows))
224 keys = tuple(component.key
for component
in components)
225 if len(set(keys)) != len(keys):
226 msg = f
"{POLICY_PATH}: duplicate component key"
227 raise ValueError(msg)
231def _series_files(component: PatchComponent, root: Path) -> tuple[Path, ...]:
232 """Validate a series file and return its patch paths in order."""
233 series_path = root / component.series
235 lines = series_path.read_text(encoding=
"utf-8").splitlines()
236 except OSError
as exc:
237 msg = f
"{component.series}: {exc}"
238 raise ValueError(msg)
from exc
240 line.strip()
for line
in lines
if line.strip()
and not line.lstrip().startswith(
"#")
242 expected = tuple(item.file
for item
in component.patches)
243 if names != expected:
244 msg = f
"{component.series}: series order {names!r} != registry {expected!r}"
245 raise ValueError(msg)
246 paths = tuple(series_path.parent / name
for name
in names)
247 missing = tuple(path.relative_to(root).as_posix()
for path
in paths
if not path.is_file())
249 msg = f
"{component.key}: missing patch files: {', '.join(missing)}"
250 raise ValueError(msg)
254def _patch_targets(patch: Path, root: Path) -> tuple[str, ...]:
255 """Return normalized target paths reported by Git's patch parser."""
256 proc = subprocess.run(
257 (trusted_git_executable(),
"apply",
"--numstat", str(patch)),
259 env=sanitized_git_environment(),
264 if proc.returncode != 0:
265 detail = proc.stderr.strip()
266 msg = f
"{patch.relative_to(root)}: git apply --numstat failed: {detail}"
267 raise ValueError(msg)
268 targets: list[str] = []
269 for line
in proc.stdout.splitlines():
270 fields = line.split(
"\t", 2)
271 if len(fields) != NUMSTAT_FIELD_COUNT
or not fields[2]:
272 msg = f
"{patch.relative_to(root)}: malformed numstat row: {line!r}"
273 raise ValueError(msg)
275 if " => " in target
or target.startswith(
"{"):
276 msg = f
"{patch.relative_to(root)}: rename patches are unsupported: {target}"
277 raise ValueError(msg)
278 _safe_rel_path(target, str(patch.relative_to(root)))
279 targets.append(target)
281 msg = f
"{patch.relative_to(root)}: patch changes no files"
282 raise ValueError(msg)
283 return tuple(dict.fromkeys(targets))
286def _blob_id(path: Path) -> str:
287 """Return the raw Git blob SHA-1 for one file, without attributes."""
288 data = path.read_bytes()
289 return hashlib.sha1(b
"blob %d\0" % len(data) + data).hexdigest()
292def _apply(patch: Path, work: Path, reverse: bool) -> str |
None:
293 """Apply one patch in a disposable component tree."""
294 argv = [trusted_git_executable(),
"apply",
"--whitespace=nowarn"]
296 argv.append(
"--reverse")
297 argv.append(str(patch))
298 proc = subprocess.run(
301 env=sanitized_git_environment(),
306 if proc.returncode == 0:
308 direction =
"reverse" if reverse
else "forward"
309 return f
"{patch}: {direction} apply failed: {proc.stderr.strip()}"
312def _copy_targets(source: Path, work: Path, targets: set[str]) -> list[str]:
313 """Copy only files touched by patches into a disposable tree."""
314 errors: list[str] = []
315 for rel
in sorted(targets):
317 if not src.is_file():
318 errors.append(f
"{source}/{rel}: declared patched file is missing")
321 dst.parent.mkdir(parents=
True, exist_ok=
True)
322 shutil.copy2(src, dst)
326def _validate_metadata(
327 component: PatchComponent, targets_by_patch: tuple[tuple[str, ...], ...]
329 """Reject a metadata classification on any code or payload file."""
330 errors: list[str] = []
331 for item, targets
in zip(component.patches, targets_by_patch, strict=
True):
332 if item.classification !=
"metadata":
334 invalid = tuple(path
for path
in targets
if PurePosixPath(path).name
not in METADATA_NAMES)
337 f
"{component.key}: {item.file} is classified metadata but changes "
343def _validate_vendored(
344 component: PatchComponent,
345 registry_component: object,
347 patch_paths: tuple[Path, ...],
350 """Prove a vendored series reverses to upstream and reapplies exactly."""
351 errors: list[str] = []
352 targets_by_patch = tuple(_patch_targets(path, root)
for path
in patch_paths)
353 errors.extend(_validate_metadata(component, targets_by_patch))
354 targets = {target
for group
in targets_by_patch
for target
in group}
355 declared = set(dict(registry_component.patched_files))
356 recorded = {entry.rel_path
for entry
in manifest.entries
if entry.kind == KIND_PATCH}
357 if targets != declared:
359 f
"{component.key}: patch targets {sorted(targets)!r} != "
360 f
"patched_files {sorted(declared)!r}"
362 if targets != recorded:
364 f
"{component.key}: patch targets {sorted(targets)!r} != "
365 f
"manifest patch rows {sorted(recorded)!r}"
369 entries = manifest.by_path()
370 source = root / registry_component.path
371 with tempfile.TemporaryDirectory(prefix=
"ra8-patch-check-")
as raw_tmp:
372 work = Path(raw_tmp) /
"component"
374 errors.extend(_copy_targets(source, work, targets))
377 for patch
in reversed(patch_paths):
378 failure = _apply(patch.resolve(), work, reverse=
True)
380 errors.append(f
"{component.key}: {failure}")
383 f
"{component.key}: reverse series does not reproduce upstream blob for {rel}"
384 for rel
in sorted(targets)
385 if _blob_id(work / rel) != entries[rel].upstream_blob
387 for patch
in patch_paths:
388 failure = _apply(patch.resolve(), work, reverse=
False)
390 errors.append(f
"{component.key}: {failure}")
392 for rel
in sorted(targets):
393 local_blob = entries[rel].local_blob
394 same_bytes = (work / rel).read_bytes() == (source / rel).read_bytes()
395 if _blob_id(work / rel) != local_blob
or not same_bytes:
397 f
"{component.key}: forward series does not reproduce vendored blob for {rel}"
402def _pin_value(path: Path, key: str) -> str |
None:
403 """Read one strict KEY=value pin from a shell-compatible pin file."""
406 line[len(prefix) :].strip().strip(
"\"'")
407 for line
in path.read_text(encoding=
"utf-8").splitlines()
408 if line.startswith(prefix)
410 return rows[0]
if len(rows) == 1
else None
416def _patch_preimages(patch: Path) -> tuple[tuple[str, str], ...]:
417 """Return (path, abbreviated pre-image blob) for every file a patch touches."""
418 pairs: list[tuple[str, str]] = []
419 current: str |
None =
None
420 for line
in patch.read_text(encoding=
"utf-8").splitlines():
421 if line.startswith(
"diff --git a/"):
422 current = line.removeprefix(
"diff --git a/").split(
" b/", 1)[0]
423 elif line.startswith(
"index ")
and current
is not None:
424 pre = line.removeprefix(
"index ").split(
"..", 1)[0].strip()
425 pairs.append((current, pre))
430def _validate_upstream_binding(
431 component: PatchComponent,
432 patch_paths: tuple[Path, ...],
433 targets_by_patch: tuple[tuple[str, ...], ...],
436 """Require every touched file to name the exact blob recorded for the pin.
438 Without this the fetched model has no offline proof at all: the series is
439 applied with --unidiff-zero, so `git apply --check` still succeeds against
440 an upstream whose target files have drifted anywhere outside the hunks.
442 recorded = dict(component.upstream_blobs)
444 return [f
"{component.key}: fetched entry records no upstream_blobs for its pin"]
445 errors: list[str] = []
446 if component.upstream_pin
is None:
447 errors.append(f
"{component.key}: fetched entry records no upstream_pin")
448 elif pin
is not None and component.upstream_pin != pin:
450 f
"{component.key}: upstream_blobs were recorded against "
451 f
"{component.upstream_pin[:12]}, but the live pin is {pin[:12]}"
453 seen: set[str] = set()
454 for patch, targets
in zip(patch_paths, targets_by_patch, strict=
True):
455 pairs = _patch_preimages(patch)
457 errors.append(f
"{component.key}: {patch.name} declares no pre-image blob")
458 indexed = {rel
for rel, _pre
in pairs}
462 f
"{component.key}: {patch.name} touches {rel} with no pre-image blob line"
463 for rel
in sorted(set(targets) - indexed)
466 for rel, pre
in pairs:
468 expected = recorded.get(rel)
469 if len(pre) < MIN_ABBREV
or not all(c
in "0123456789abcdef" for c
in pre):
471 f
"{component.key}: {patch.name} pre-image for {rel} is not a usable "
472 f
"blob abbreviation: {pre!r}"
474 elif expected
is None:
476 f
"{component.key}: {patch.name} touches {rel}, which has no recorded "
477 "upstream blob for the pin"
479 elif not expected.startswith(pre):
481 f
"{component.key}: {patch.name} expects {rel} at {pre}, but the pin "
482 f
"records {expected[: len(pre)]}"
485 f
"{component.key}: upstream_blobs records {rel}, which no patch in the series touches"
486 for rel
in sorted(set(recorded) - seen)
491def _validate_fetched(
492 component: PatchComponent, patch_paths: tuple[Path, ...], root: Path
494 """Verify a fetched dependency connects its pin, series, and build script."""
495 apply_script = component.apply_script
496 series_token = component.series_token
497 pin_file = component.pin_file
498 pin_key = component.pin_key
499 if apply_script
is None or series_token
is None or pin_file
is None or pin_key
is None:
501 f
"{component.key}: fetched entry requires apply_script, series_token, "
502 "pin_file, and pin_key"
504 targets_by_patch = tuple(_patch_targets(path, root)
for path
in patch_paths)
505 errors = _validate_metadata(component, targets_by_patch)
506 script_path = root / apply_script
507 pin_path = root / pin_file
508 if not script_path.is_file()
or not pin_path.is_file():
509 return [*errors, f
"{component.key}: apply script or pin file is missing"]
510 script = script_path.read_text(encoding=
"utf-8")
511 pin = _pin_value(pin_path, pin_key)
512 if pin
is None or not HEX40_RE.fullmatch(pin):
513 errors.append(f
"{component.key}: {component.pin_file}:{pin_key} is not one full 40-hex pin")
515 errors.extend(_validate_upstream_binding(component, patch_paths, targets_by_patch, pin))
517 script.find(pin_key),
518 script.find(series_token),
519 script.find(
"git -C"),
520 script.find(
" apply "),
522 if min(positions) < 0
or positions[0] >= positions[1]:
524 f
"{component.key}: build script does not connect pin-before-series and git apply"
526 direct = re.findall(
r"patches/[0-9]{4}-[a-z0-9-]+\.patch", script)
529 f
"{component.key}: build script hard-codes patches "
530 f
"instead of consuming series: {direct}"
532 errors.append(detail)
536def check(root: Path = REPO_ROOT) -> list[str]:
537 """Return all patch-policy violations in root."""
539 policy = load_policy(root)
540 except (TypeError, ValueError)
as exc:
543 component.key: component
544 for component
in REGISTRY
545 if component.provenance != PROV_NOT_VENDORED
547 declared_patched = {key
for key, component
in registry.items()
if component.patched_files}
548 policy_vendored = {component.key
for component
in policy
if component.delivery ==
"vendored"}
549 errors: list[str] = []
550 if declared_patched != policy_vendored:
552 f
"vendored patch policy keys {sorted(policy_vendored)!r} != "
553 f
"registry patched keys {sorted(declared_patched)!r}"
555 registered_patch_files: set[Path] = set()
556 for component
in policy:
558 patch_paths = _series_files(component, root)
559 registered_patch_files.update(path.relative_to(root)
for path
in patch_paths)
560 if component.delivery ==
"fetched":
561 errors.extend(_validate_fetched(component, patch_paths, root))
563 registry_component = registry.get(component.key)
564 if registry_component
is None:
566 f
"{component.key}: vendored patch policy has no SBOM registry component"
569 manifest_file = root / manifest_path(component.key)
570 manifest = parse_manifest(
572 manifest_file.read_text(encoding=
"utf-8"),
573 manifest_path(component.key),
576 _validate_vendored(component, registry_component, manifest, patch_paths, root)
578 except (ManifestError, OSError, ValueError)
as exc:
579 errors.append(str(exc))
580 patch_roots = (root /
"docs/sbom/patches", root /
"coprocessor")
582 path.relative_to(root)
583 for scan_root
in patch_roots
584 if scan_root.exists()
585 for path
in scan_root.rglob(
"*.patch")
586 if "build" not in path.parts
and "upstream" not in path.parts
588 unregistered = discovered - registered_patch_files
590 rendered =
", ".join(sorted(path.as_posix()
for path
in unregistered))
591 errors.append(f
"unregistered first-party patch files: {rendered}")
595def _selftest_patch() -> str:
596 """Return a minimal patch used by the both-directions fixture."""
597 return """diff --git a/src/value.c b/src/value.c
598index 788b307..e58e70c 100644
607def _selftest_vendored_replay(
609) -> tuple[list[str], list[str], list[str], Path, str, str]:
610 """Return the vendored-replay directions plus the shared fetched fixture inputs."""
611 patch_dir = root /
"patches"
612 source = root /
"vendor"
613 patch_dir = root /
"patches"
614 (source /
"src").mkdir(parents=
True)
616 current = source /
"src/value.c"
617 current.write_text(
"int value = 2;\n", encoding=
"utf-8")
618 patch = patch_dir /
"0001-change-value.patch"
619 patch.write_text(_selftest_patch(), encoding=
"utf-8")
620 upstream_blob = hashlib.sha1(
621 b
"blob 15\0int value = 1;\n"
623 local_blob = _blob_id(current)
624 entry = SimpleNamespace(
626 rel_path=
"src/value.c",
627 upstream_blob=upstream_blob,
628 local_blob=local_blob,
630 manifest = SimpleNamespace(entries=(entry,), by_path=
lambda: {entry.rel_path: entry})
631 registry_component = SimpleNamespace(
632 path=
"vendor", patched_files=((entry.rel_path,
"fixture"),)
634 component = PatchComponent(
637 Path(
"patches/series"),
638 (PatchItem(patch.name,
"functional"),),
640 clean = _validate_vendored(component, registry_component, manifest, (patch,), root)
641 current.write_text(
"int value = 3;\n", encoding=
"utf-8")
642 drift = _validate_vendored(component, registry_component, manifest, (patch,), root)
643 metadata = PatchComponent(
647 (PatchItem(patch.name,
"metadata"),),
649 current.write_text(
"int value = 2;\n", encoding=
"utf-8")
650 mislabeled = _validate_vendored(metadata, registry_component, manifest, (patch,), root)
651 return clean, drift, mislabeled, patch_dir, upstream_blob, local_blob
654def _fetched_fixture_root(
655 base: Path, blobs: dict[str, str], pin: str, recorded_pin: str, patch_text: str
657 """Materialize a complete fetched-component tree for one selftest direction."""
659 (root /
"docs/sbom/patches").mkdir(parents=
True, exist_ok=
True)
660 (root /
"coprocessor/fix/patches").mkdir(parents=
True, exist_ok=
True)
661 (root /
"coprocessor/fix/patches/0001-fixture.patch").write_text(patch_text, encoding=
"utf-8")
662 (root /
"coprocessor/fix/patches/series").write_text(
"0001-fixture.patch\n", encoding=
"utf-8")
663 (root /
"coprocessor/fix/pins.env").write_text(f
"FIXTURE_COMMIT={pin}\n", encoding=
"utf-8")
664 (root /
"coprocessor/fix/build.sh").write_text(
665 '#!/bin/sh\n. ./pins.env\n: "$FIXTURE_COMMIT"\n'
666 'while read -r p; do git -C "$c" apply "patches/$p"; done < patches/series\n',
669 rows =
"\n".join(f
'"{rel}" = "{blob}"' for rel, blob
in sorted(blobs.items()))
670 (root /
"docs/sbom/patches/registry.toml").write_text(
671 "schema_version = 1\n\n[[component]]\n"
672 'key = "fixture"\ndelivery = "fetched"\n'
673 'series = "coprocessor/fix/patches/series"\n'
674 'apply_script = "coprocessor/fix/build.sh"\n'
675 'series_token = "patches/series"\n'
676 'pin_file = "coprocessor/fix/pins.env"\n'
677 'pin_key = "FIXTURE_COMMIT"\n'
678 + (f
'upstream_pin = "{recorded_pin}"\n' if recorded_pin
else "")
679 + (f
"\n[component.upstream_blobs]\n{rows}\n" if rows
else "")
680 +
'\n[[component.patches]]\nfile = "0001-fixture.patch"\n'
681 'classification = "functional"\n',
687@dataclass(frozen=True)
688class _FetchedFixture:
689 """One fetched-component selftest direction, as data."""
692 blobs: dict[str, str]
698def _fetched_binding_errors(base: Path, case: _FetchedFixture) -> list[str]:
699 """Run the REAL entry point CI runs and return only fetched-binding errors.
701 Driving check() rather than the private helper is the point: deleting the
702 production call site must make these cases fail, which calling the helper
703 directly could never detect.
705 root = _fetched_fixture_root(
706 base / case.name, case.blobs, case.pin, case.recorded_pin, case.patch_text
708 return [error
for error
in check(root)
if not error.startswith(
"vendored patch policy keys")]
711def _selftest_fetched_binding(
712 base: Path, upstream_blob: str, local_blob: str
713) -> dict[str, list[str]]:
714 """Return every direction of the fetched pin binding, keyed by case name."""
716 other = (
"f" if upstream_blob[0] !=
"f" else "0") + upstream_blob[1:]
718 "diff --git a/src/value.c b/src/value.c\n"
719 f
"index {upstream_blob[:7]}..{local_blob[:7]} 100644\n"
720 "--- a/src/value.c\n+++ b/src/value.c\n"
721 "@@ -1 +1 @@\n-int value = 1;\n+int value = 2;\n"
723 blank = good.replace(f
"index {upstream_blob[:7]}..",
"index ..", 1)
725 "diff --git a/src/other.c b/src/other.c\n"
726 "--- a/src/other.c\n+++ b/src/other.c\n"
727 "@@ -1 +1 @@\n-int other = 1;\n+int other = 2;\n"
729 one = {
"src/value.c": upstream_blob}
731 "clean": _fetched_binding_errors(base, _FetchedFixture(
"clean", one, pin, pin, good)),
732 "drift": _fetched_binding_errors(
733 base, _FetchedFixture(
"drift", {
"src/value.c": other}, pin, pin, good)
735 "unrecorded": _fetched_binding_errors(
736 base, _FetchedFixture(
"unrec", {
"src/other.c": other}, pin, pin, good)
738 "empty": _fetched_binding_errors(base, _FetchedFixture(
"empty", {}, pin, pin, good)),
739 "pin-moved": _fetched_binding_errors(
740 base, _FetchedFixture(
"pinmv", one,
"1" * 40, pin, good)
742 "blank-preimage": _fetched_binding_errors(
743 base, _FetchedFixture(
"blank", one, pin, pin, blank)
745 "no-index-line": _fetched_binding_errors(
746 base, _FetchedFixture(
"noidx", one, pin, pin, extra)
751def selftest() -> int:
752 """Prove exact replay and representative failure directions."""
753 with tempfile.TemporaryDirectory(prefix=
"ra8-patch-selftest-")
as raw_tmp:
755 clean, drift, mislabeled, _patch_dir, upstream_blob, local_blob = _selftest_vendored_replay(
759 fetched = _selftest_fetched_binding(root /
"fetched", upstream_blob, local_blob)
762 failures.append(f
"recorded pin blob was rejected: {fetched['clean']}")
764 f
"fetched pin binding did not fail: {case}"
765 for case
in (
"drift",
"unrecorded",
"empty",
"pin-moved",
"blank-preimage",
"no-index-line")
769 failures.append(f
"clean fixture failed: {clean}")
771 failures.append(
"vendored-byte drift did not fail")
772 if not any(
"classified metadata" in error
for error
in mislabeled):
773 failures.append(
"metadata classification on source did not fail")
775 print(
"check_third_party_patches: selftest FAILED", file=sys.stderr)
776 for failure
in failures:
777 print(f
" {failure}", file=sys.stderr)
780 "check_third_party_patches: selftest passed "
781 "(vendored replay + fetched pin binding, both directions)."
787 """CLI entry point."""
788 parser = argparse.ArgumentParser(description=__doc__)
790 "--selftest", action=
"store_true", help=
"run isolated both-directions tests"
792 args = parser.parse_args()
798 f
"check_third_party_patches: FAIL ({len(errors)} finding(s))",
802 print(f
" {error}", file=sys.stderr)
804 print(
"check_third_party_patches: PASS -- every reviewed series reproduces its declared bytes.")
808if __name__ ==
"__main__":
809 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.