4"""Apply-check the C6 patch series against its exact fetched upstream pin.
6The per-push patch gate is deliberately offline: it proves the pin, numbered
7series, and build entry point remain connected. This networked companion runs
8in the controlled weekly SOUP refresh job. It fetches only the exact immutable
9commit into a disposable repository, verifies the checkout identity, then
10applies the committed series without building, flashing, or contacting a rig.
13from __future__
import annotations
21from pathlib
import Path
23REPO_ROOT = Path(__file__).resolve().parents[2]
24sys.path.insert(0, str(REPO_ROOT /
"scripts" /
"dev"))
26from git_environment
import sanitized_git_environment
28C6_DIR = REPO_ROOT /
"coprocessor" /
"esp32c6"
29PINS_FILE = C6_DIR /
"pins.env"
30SERIES_FILE = C6_DIR /
"patches" /
"series"
31EXPECTED_UPSTREAM =
"https://github.com/espressif/esp-hosted-mcu"
32PATCH_NAME_RE = re.compile(
r"^[0-9]{4}-[a-z0-9][a-z0-9-]*\.patch$")
33COMMIT_RE = re.compile(
r"^[0-9a-f]{40}$")
36def _git_environment() -> dict[str, str]:
37 """Return a noninteractive Git environment with no host-level filters."""
38 environment = sanitized_git_environment()
41 "GIT_CONFIG_GLOBAL": os.devnull,
42 "GIT_CONFIG_NOSYSTEM":
"1",
43 "GIT_LFS_SKIP_SMUDGE":
"1",
44 "GIT_TERMINAL_PROMPT":
"0",
50def _git(cwd: Path, *args: str, timeout: int = 300) -> subprocess.CompletedProcess[str]:
51 """Run one bounded Git command in the disposable repository."""
52 return subprocess.run(
56 "core.hooksPath=/dev/null",
58 "http.followRedirects=false",
60 "http.sslVerify=true",
62 "protocol.file.allow=never",
64 "protocol.ext.allow=never",
69 env=_git_environment(),
77def _pin(text: str, name: str) -> str:
78 """Return one unquoted strict KEY=value row."""
80 values = [line[len(prefix) :]
for line
in text.splitlines()
if line.startswith(prefix)]
81 if len(values) != 1
or not values[0]
or any(char.isspace()
for char
in values[0]):
82 msg = f
"{PINS_FILE.relative_to(REPO_ROOT)}: expected one strict {name}=value row"
87def load_inputs() -> tuple[str, str, tuple[Path, ...]]:
88 """Load and validate the allowlisted upstream, full commit, and series."""
89 pins = PINS_FILE.read_text(encoding=
"utf-8")
90 upstream = _pin(pins,
"ESP_HOSTED_MCU_URL")
91 commit = _pin(pins,
"ESP_HOSTED_MCU_COMMIT")
92 if upstream != EXPECTED_UPSTREAM:
93 msg = f
"refusing unallowlisted C6 upstream: {upstream!r}"
95 if not COMMIT_RE.fullmatch(commit):
96 msg =
"ESP_HOSTED_MCU_COMMIT must be one full lowercase 40-hex commit"
100 for line
in SERIES_FILE.read_text(encoding=
"utf-8").splitlines()
101 if line.strip()
and not line.lstrip().startswith(
"#")
103 if not names
or len(names) != len(set(names)):
104 msg =
"C6 patch series must be nonempty and contain no duplicates"
105 raise ValueError(msg)
106 if any(PATCH_NAME_RE.fullmatch(name)
is None for name
in names):
107 msg =
"C6 patch series contains a non-numbered patch name"
108 raise ValueError(msg)
109 patches = tuple(SERIES_FILE.parent / name
for name
in names)
110 missing = [path.name
for path
in patches
if not path.is_file()]
112 msg = f
"C6 patch series is missing: {', '.join(missing)}"
113 raise ValueError(msg)
114 return upstream, commit, patches
117def check_checkout(checkout: Path, commit: str, patches: tuple[Path, ...]) -> list[str]:
118 """Verify identity, non-vacuous apply, and exact reverse replay."""
119 findings: list[str] = []
120 identity = _git(checkout,
"rev-parse",
"HEAD")
121 actual = identity.stdout.strip()
if identity.returncode == 0
else ""
123 findings.append(f
"checkout is {actual or 'unreadable'}, expected exact pin {commit}")
126 return [
"patch series is empty; applicability proof would be vacuous"]
127 for patch
in patches:
128 check = _git(checkout,
"apply",
"--unidiff-zero",
"--check", str(patch.resolve()))
129 if check.returncode != 0:
130 findings.append(f
"{patch.name}: apply-check failed: {check.stderr.strip()}")
132 apply = _git(checkout,
"apply",
"--unidiff-zero", str(patch.resolve()))
133 if apply.returncode != 0:
134 findings.append(f
"{patch.name}: checked but could not apply: {apply.stderr.strip()}")
138 changed = _git(checkout,
"diff",
"--quiet",
"--exit-code")
139 if changed.returncode == 0:
140 return [
"applied series leaves no changed bytes; applicability proof is vacuous"]
141 if changed.returncode != 1:
142 return [f
"could not inspect applied patch bytes: {changed.stderr.strip()}"]
143 for patch
in reversed(patches):
144 reverse_check = _git(
150 str(patch.resolve()),
152 if reverse_check.returncode != 0:
154 f
"{patch.name}: reverse apply-check failed: {reverse_check.stderr.strip()}"
162 str(patch.resolve()),
164 if reverse.returncode != 0:
165 findings.append(f
"{patch.name}: reverse apply failed: {reverse.stderr.strip()}")
167 restored = _git(checkout,
"status",
"--porcelain=v1",
"--untracked-files=all")
168 if not findings
and (restored.returncode != 0
or restored.stdout):
169 findings.append(
"reverse replay did not restore the exact clean pinned checkout")
173def verify_upstream() -> list[str]:
174 """Fetch only the exact pin and apply-check the series in a temp tree."""
175 upstream, commit, patches = load_inputs()
176 with tempfile.TemporaryDirectory(prefix=
"ra8-c6-patch-upstream-")
as raw_tmp:
177 checkout = Path(raw_tmp) /
"esp-hosted-mcu"
179 init = _git(checkout,
"init",
"--quiet")
180 if init.returncode != 0:
181 return [f
"git init failed: {init.stderr.strip()}"]
191 if fetch.returncode != 0:
192 return [f
"exact-pin fetch failed: {fetch.stderr.strip()}"]
193 checkout_pin = _git(checkout,
"checkout",
"--quiet",
"--detach",
"FETCH_HEAD")
194 if checkout_pin.returncode != 0:
195 return [f
"exact-pin checkout failed: {checkout_pin.stderr.strip()}"]
196 return check_checkout(checkout, commit, patches)
199def _fixture(root: Path, content: str) -> str:
200 """Create one committed source tree and return its exact commit."""
202 if _git(root,
"init",
"--quiet").returncode != 0:
203 msg =
"selftest git init failed"
204 raise RuntimeError(msg)
205 (root /
"source.txt").write_text(content, encoding=
"ascii")
207 (
"config",
"user.email",
"selftest@invalid"),
208 (
"config",
"user.name",
"selftest"),
209 (
"add",
"source.txt"),
210 (
"commit",
"--quiet",
"-m",
"fixture"),
212 result = _git(root, *args)
213 if result.returncode != 0:
214 msg = f
"selftest git {' '.join(args)} failed: {result.stderr}"
215 raise RuntimeError(msg)
216 return _git(root,
"rev-parse",
"HEAD").stdout.strip()
219def selftest() -> int:
220 """Prove apply/reverse succeeds and context, identity, or scope drift fires."""
221 failures: list[str] = []
222 patch_text =
"""diff --git a/source.txt b/source.txt
229 with tempfile.TemporaryDirectory(prefix=
"ra8-c6-patch-selftest-")
as raw_tmp:
231 patch = root /
"0001-fixture.patch"
232 patch.write_text(patch_text, encoding=
"ascii")
234 good_pin = _fixture(good,
"before\n")
235 if check_checkout(good, good_pin, (patch,)):
236 failures.append(
"exact-pin applicable patch did not stay quiet")
237 drift = root /
"drift"
238 drift_pin = _fixture(drift,
"different\n")
239 if not check_checkout(drift, drift_pin, (patch,)):
240 failures.append(
"patch context drift did not fire")
241 wrong_pin = root /
"wrong-pin"
242 actual_pin = _fixture(wrong_pin,
"before\n")
243 if not check_checkout(wrong_pin,
"0" * len(actual_pin), (patch,)):
244 failures.append(
"checkout identity drift did not fire")
245 empty = root /
"empty"
246 empty_pin = _fixture(empty,
"before\n")
247 if not check_checkout(empty, empty_pin, ()):
248 failures.append(
"empty patch series did not fire")
250 print(
"check_c6_patch_upstream.py --selftest: FAIL", file=sys.stderr)
251 for failure
in failures:
252 print(f
" {failure}", file=sys.stderr)
254 print(
"check_c6_patch_upstream.py --selftest: PASS (4 cases, both directions)")
259 """Run isolated selftests or the controlled exact-pin network check."""
260 parser = argparse.ArgumentParser(description=__doc__)
261 group = parser.add_mutually_exclusive_group(required=
True)
262 group.add_argument(
"--selftest", action=
"store_true")
263 group.add_argument(
"--verify-upstream", action=
"store_true")
264 args = parser.parse_args()
268 findings = verify_upstream()
269 except (OSError, ValueError, subprocess.SubprocessError)
as exc:
270 findings = [str(exc)]
272 print(
"check_c6_patch_upstream.py: FAIL", file=sys.stderr)
273 for finding
in findings:
274 print(f
" {finding}", file=sys.stderr)
276 _upstream, commit, patches = load_inputs()
278 f
"check_c6_patch_upstream.py: {len(patches)} non-vacuous patch(es) "
279 f
"apply and reverse cleanly at exact pin {commit}"
284if __name__ ==
"__main__":
285 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.