From 15cc2931699e4a3900f519c6c5d3f02715a471cb Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:08:20 +0200 Subject: [PATCH 1/7] rdte: add G3 provider CreateInstance boundary probe --- ...uenas_middleware_garm_provider_g3_probe.py | 540 ++++++++++++++++++ 1 file changed, 540 insertions(+) create mode 100644 scripts/truenas_middleware_garm_provider_g3_probe.py diff --git a/scripts/truenas_middleware_garm_provider_g3_probe.py b/scripts/truenas_middleware_garm_provider_g3_probe.py new file mode 100644 index 000000000..c51e6b8e6 --- /dev/null +++ b/scripts/truenas_middleware_garm_provider_g3_probe.py @@ -0,0 +1,540 @@ +#!/usr/bin/env python3 +"""G3 oracle: exact provider-created runner App to the synthetic GitHub credential boundary.""" +from __future__ import annotations + +import argparse +import copy +import json +import os +import pathlib +import secrets +import subprocess +import tempfile +import time + +import truenas_middleware_garm_provider_g2_probe as g2 + + +EXPECTED_VERSION = "TrueNAS-26.0.0-BETA.3" +EXPECTED_APPLIANCE = ( + "ghcr.io/sempersupra/garm-appliance@" + "sha256:1af67841ddd4589e3798dcda8be49230565c849d07ab57fd05899432dcdabca9" +) +EXPECTED_PROVIDER_PRODUCT_SOURCE = "14535745dc3aa3c0b5466da7c704bca4d23dcec5" +EXPECTED_PROVIDER_BINARY_SHA256 = "911f076ba9421f7bb2a1ea72acc927327d799a5ad6265bffe2d869b2cbf5ecc2" +EXPECTED_FIXTURE_SCHEMA = "semper-supra.garm-provider-truenas-g3-create-fixture/1" +EXPECTED_CONTROLLER_ID = "g3-controller" +EXPECTED_POOL_ID = "g3-pool" +DEFAULT_CALLBACK_URL = "https://httpbin.org/anything/sempersupra-g3/status" +DEFAULT_METADATA_URL = "https://httpbin.org/anything/sempersupra-g3/metadata" +EXPECTED_SUBSTITUTIONS = [ + "bootstrap_template.callback-url", + "bootstrap_template.metadata-url", + "bootstrap_template.instance-token", + "expected_compose_template.services.runner.environment.GARM_CALLBACK_URL", + "expected_compose_template.services.runner.environment.GARM_METADATA_URL", + "expected_compose_template.services.runner.environment.GARM_INSTANCE_TOKEN", +] + + +def load_bundle(directory: pathlib.Path, expected_producer: str) -> dict: + path = directory / "garm-provider-g3-fixture.json" + bundle = json.loads(path.read_text(encoding="utf-8")) + if bundle.get("schema") != EXPECTED_FIXTURE_SCHEMA: + raise RuntimeError("unexpected G3 fixture schema") + if bundle.get("provider_product_source") != EXPECTED_PROVIDER_PRODUCT_SOURCE: + raise RuntimeError("G3 fixture product source drifted") + if bundle.get("producer_source") != expected_producer: + raise RuntimeError("G3 fixture producer source drifted") + if bundle.get("controller_id") != EXPECTED_CONTROLLER_ID: + raise RuntimeError("G3 fixture controller identity drifted") + if bundle.get("pool_id") != EXPECTED_POOL_ID: + raise RuntimeError("G3 fixture pool identity drifted") + if bundle.get("run_local_substitutions") != EXPECTED_SUBSTITUTIONS: + raise RuntimeError("G3 run-local substitution allowlist drifted") + if not isinstance(bundle.get("bootstrap_template"), dict): + raise RuntimeError("G3 fixture has no bootstrap template") + if not isinstance(bundle.get("expected_compose_template"), dict): + raise RuntimeError("G3 fixture has no expected Compose template") + if not isinstance(bundle.get("expected_app_name"), str) or not bundle["expected_app_name"]: + raise RuntimeError("G3 fixture has no expected App name") + return bundle + + +def set_path(root: dict, dotted: str, value: str) -> None: + parts = dotted.split(".") + cur = root + for part in parts[:-1]: + nxt = cur.get(part) + if not isinstance(nxt, dict): + raise RuntimeError(f"G3 substitution path is not an object: {dotted}") + cur = nxt + if parts[-1] not in cur: + raise RuntimeError(f"G3 substitution target absent: {dotted}") + cur[parts[-1]] = value + + +def lower_fixture(bundle: dict, callback_url: str, metadata_url: str, token: str) -> tuple[dict, dict]: + bootstrap = copy.deepcopy(bundle["bootstrap_template"]) + expected = copy.deepcopy(bundle["expected_compose_template"]) + values = { + "bootstrap_template.callback-url": callback_url, + "bootstrap_template.metadata-url": metadata_url, + "bootstrap_template.instance-token": token, + "expected_compose_template.services.runner.environment.GARM_CALLBACK_URL": callback_url, + "expected_compose_template.services.runner.environment.GARM_METADATA_URL": metadata_url, + "expected_compose_template.services.runner.environment.GARM_INSTANCE_TOKEN": token, + } + for dotted in EXPECTED_SUBSTITUTIONS: + target = bootstrap if dotted.startswith("bootstrap_template.") else expected + relative = dotted.split(".", 1)[1] + set_path(target, relative, values[dotted]) + return bootstrap, expected + + +def provider_env( + config_path: pathlib.Path, + api_key: str, + ca_path: pathlib.Path, + command: str, + instance_id: str | None = None, +) -> dict[str, str]: + env = os.environ.copy() + env.update({ + "GARM_INTERFACE_VERSION": "v0.1.1", + "GARM_COMMAND": command, + "GARM_CONTROLLER_ID": EXPECTED_CONTROLLER_ID, + "GARM_POOL_ID": EXPECTED_POOL_ID, + "GARM_PROVIDER_CONFIG_FILE": str(config_path), + "TRUENAS_API_KEY": api_key, + "SSL_CERT_FILE": str(ca_path), + }) + if instance_id: + env["GARM_INSTANCE_ID"] = instance_id + else: + env.pop("GARM_INSTANCE_ID", None) + return env + + +def invoke_provider( + provider_binary: pathlib.Path, + config_path: pathlib.Path, + api_key: str, + ca_path: pathlib.Path, + command: str, + *, + instance_id: str | None = None, + stdin_object: dict | None = None, +) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [str(provider_binary)], + env=provider_env(config_path, api_key, ca_path, command, instance_id), + input=(json.dumps(stdin_object) if stdin_object is not None else None), + text=True, + capture_output=True, + check=False, + timeout=120, + ) + + +def parse_provider_json(cp: subprocess.CompletedProcess[str], label: str, token: str): + if cp.returncode != 0: + detail = (cp.stderr or cp.stdout or "")[-1200:].replace(token, "") + raise RuntimeError(f"{label} failed rc={cp.returncode}: {detail}") + text = cp.stdout.strip() + if not text: + raise RuntimeError(f"{label} returned empty stdout") + try: + return json.loads(text) + except json.JSONDecodeError as exc: + raise RuntimeError(f"{label} stdout was not JSON") from exc + + +def query_app(session: g2.AdminSession, name: str) -> dict | None: + matches = session.call("app.query", [[["id", "=", name]]]) + if not matches: + return None + return matches[0] + + +def ensure_inactive(session: g2.AdminSession, name: str, job_timeout: float) -> str: + app = query_app(session, name) + if app is None: + raise RuntimeError("provider-created App disappeared before retirement") + state = str(app.get("state") or "") + if state in {"STOPPED", "CRASHED"}: + return state + stop_id = session.call("app.stop", [name]) + if not isinstance(stop_id, int): + raise RuntimeError("app.stop did not return a job id") + session.wait_job(stop_id, f"app.stop {name}", job_timeout) + app = query_app(session, name) + if app is None: + raise RuntimeError("provider-created App disappeared after stop") + state = str(app.get("state") or "") + if state not in {"STOPPED", "CRASHED"}: + raise RuntimeError(f"provider-created App did not become inactive: {state}") + return state + + +def main() -> int: + p = argparse.ArgumentParser() + p.add_argument("--host", default="127.0.0.1") + p.add_argument("--http-port", type=int, required=True) + p.add_argument("--https-port", type=int, required=True) + p.add_argument("--password-file", required=True) + p.add_argument("--fixture-dir", type=pathlib.Path, required=True) + p.add_argument("--fixture-producer-commit", required=True) + p.add_argument("--callback-url", default=DEFAULT_CALLBACK_URL) + p.add_argument("--metadata-url", default=DEFAULT_METADATA_URL) + p.add_argument("--out", required=True) + p.add_argument("--timeout", type=float, default=8.0) + p.add_argument("--job-timeout", type=float, default=300.0) + a = p.parse_args() + + started = time.time() + payload = { + "schema": "truenas-garm-provider-g3/v1", + "classification": "ORACLE_FAILURE", + "oracleSatisfied": False, + "expected_version": EXPECTED_VERSION, + "provider_product_source": EXPECTED_PROVIDER_PRODUCT_SOURCE, + "provider_binary_sha256": EXPECTED_PROVIDER_BINARY_SHA256, + "appliance": EXPECTED_APPLIANCE, + "controller_id": EXPECTED_CONTROLLER_ID, + "pool_id": EXPECTED_POOL_ID, + "provider_create_exercised": False, + "provider_delete_exercised": False, + "github_credentials_present": False, + "github_jit_registration_exercised": False, + "private_repository_execution": False, + "physical_truenas_mutation": False, + "capacity_promotion": False, + "secret_values_recorded": False, + "synthetic_token_recorded": False, + "callback_fixture": { + "callback_url": a.callback_url, + "metadata_url": a.metadata_url, + "authority": "public-synthetic-non-github", + }, + } + + if g2.EXPECTED_APPLIANCE != EXPECTED_APPLIANCE: + raise RuntimeError("G2 helper appliance identity drifted") + if g2.EXPECTED_PROVIDER_BINARY_SHA256 != EXPECTED_PROVIDER_BINARY_SHA256: + raise RuntimeError("G2 helper provider binary identity drifted") + + password = pathlib.Path(a.password_file).read_text(encoding="utf-8").strip() + session = g2.AdminSession(a.host, a.http_port, password, a.timeout) + api_key_id: int | None = None + api_key_value: str | None = None + imported_cert_id: int | None = None + original_ui_cert_id: int | None = None + app_name: str | None = None + provider_delete_succeeded = False + fallback_cleanup_used = False + cleanup_errors: list[str] = [] + + with tempfile.TemporaryDirectory(prefix="garm-provider-g3-") as td: + root = pathlib.Path(td) + token = f"g3-public-{secrets.token_hex(12)}" + try: + bundle = load_bundle(a.fixture_dir, a.fixture_producer_commit) + payload["fixture_producer_source"] = bundle["producer_source"] + payload["fixture_bundle_sha256"] = g2.sha256_file( + a.fixture_dir / "garm-provider-g3-fixture.json" + ) + payload["expected_app_name"] = bundle["expected_app_name"] + payload["run_local_substitutions"] = EXPECTED_SUBSTITUTIONS + + bootstrap, expected_compose = lower_fixture( + bundle, a.callback_url, a.metadata_url, token + ) + expected_compose_sha = g2.canonical_json_sha256(expected_compose) + + provider_binary = g2.extract_provider_binary(root) + payload["provider_binary_from_appliance"] = True + + cert_path, key_path = g2.generate_tls(root) + payload["transport"] = { + "scheme": "wss", + "host": a.host, + "port": a.https_port, + "insecure_skip_verify": False, + "certificate_sha256": g2.sha256_file(cert_path), + "private_key_recorded": False, + "api_key_recorded": False, + } + + session.connect() + version = session.call("system.version", []) + payload["system_version"] = version + if version != EXPECTED_VERSION: + raise RuntimeError(f"target version drifted: {version}") + + general = session.call("system.general.config", []) + original_ui_cert_id = g2.ui_cert_id(general.get("ui_certificate")) + + api_key = session.call("api_key.create", [{ + "name": f"g3-provider-{secrets.token_hex(5)}", + "username": "truenas_admin", + }]) + if not isinstance(api_key, dict) or not isinstance(api_key.get("id"), int): + raise RuntimeError("api_key.create did not return an id") + api_key_id = int(api_key["id"]) + api_key_value = api_key.get("key") + if not isinstance(api_key_value, str) or not api_key_value: + raise RuntimeError("api_key.create did not return a key") + + create_cert_id = session.call("certificate.create", [{ + "name": f"g3_provider_{secrets.token_hex(4)}", + "create_type": "CERTIFICATE_CREATE_IMPORTED", + "add_to_trusted_store": False, + "certificate": cert_path.read_text(encoding="utf-8"), + "privatekey": key_path.read_text(encoding="utf-8"), + }]) + if not isinstance(create_cert_id, int): + raise RuntimeError("certificate.create did not return a job id") + cert_job = session.wait_job(create_cert_id, "certificate.create", a.job_timeout) + cert_result = cert_job.get("result") + if not isinstance(cert_result, dict) or not isinstance(cert_result.get("id"), int): + raise RuntimeError("certificate.create job did not return certificate id") + imported_cert_id = int(cert_result["id"]) + + session.call("system.general.update", [{"ui_certificate": imported_cert_id}]) + try: + session.call("system.general.ui_restart", [0]) + except Exception: + pass + session.close() + g2.wait_verified_https(a.host, a.https_port, cert_path) + + provider_config = { + "mode": "truenas", + "truenas": { + "host": a.host, + "username": "truenas_admin", + "api_key_env": "TRUENAS_API_KEY", + "port": a.https_port, + "insecure_skip_verify": False, + "callback_host_gateway": False, + }, + } + config_path = root / "provider.json" + config_path.write_text( + json.dumps(provider_config, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + config_path.chmod(0o600) + + session.connect() + payload["provider_create_exercised"] = True + create_cp = invoke_provider( + provider_binary, config_path, api_key_value, cert_path, + "CreateInstance", stdin_object=bootstrap, + ) + created = parse_provider_json(create_cp, "CreateInstance", token) + if not isinstance(created, dict): + raise RuntimeError("CreateInstance did not return an object") + app_name = str(created.get("provider_id") or "") + if app_name != bundle["expected_app_name"]: + raise RuntimeError( + f"provider-created App identity drifted: {app_name!r}" + ) + + config = session.call("app.config", [app_name]) + if not isinstance(config, dict): + raise RuntimeError("app.config did not return an object") + realized_sha = g2.canonical_json_sha256(config) + if realized_sha != expected_compose_sha: + raise RuntimeError("provider-created exact Compose read-back drifted") + + get_cp = invoke_provider( + provider_binary, config_path, api_key_value, cert_path, + "GetInstance", instance_id=app_name, + ) + got = parse_provider_json(get_cp, "GetInstance created", token) + if not isinstance(got, dict) or got.get("provider_id") != app_name: + raise RuntimeError("GetInstance did not read back created provider identity") + + list_cp = invoke_provider( + provider_binary, config_path, api_key_value, cert_path, + "ListInstances", + ) + listed = parse_provider_json(list_cp, "ListInstances created", token) + if not isinstance(listed, list): + raise RuntimeError("ListInstances did not return an array") + ids = sorted( + str(item.get("provider_id")) + for item in listed + if isinstance(item, dict) + ) + if ids != [app_name]: + raise RuntimeError(f"ListInstances created inventory drifted: {ids}") + + inactive_state = ensure_inactive(session, app_name, a.job_timeout) + + payload["provider_delete_exercised"] = True + delete_cp = invoke_provider( + provider_binary, config_path, api_key_value, cert_path, + "DeleteInstance", instance_id=app_name, + ) + if delete_cp.returncode != 0: + detail = (delete_cp.stderr or delete_cp.stdout or "")[-1200:].replace( + token, "" + ) + raise RuntimeError( + f"DeleteInstance failed rc={delete_cp.returncode}: {detail}" + ) + provider_delete_succeeded = True + + if query_app(session, app_name) is not None: + raise RuntimeError("provider-created App remains after DeleteInstance") + + list_after_cp = invoke_provider( + provider_binary, config_path, api_key_value, cert_path, + "ListInstances", + ) + listed_after = parse_provider_json( + list_after_cp, "ListInstances post-delete", token + ) + if listed_after != []: + raise RuntimeError( + f"provider inventory not empty after delete: {listed_after!r}" + ) + + payload["oracles"] = { + "verified_tls_transport": True, + "api_key_auth_transport": True, + "create_instance_returned_exact_owned_identity": True, + "exact_realized_compose_readback": True, + "get_created_instance": True, + "list_created_instance_only": True, + "inactive_transition_via_supported_middleware": True, + "provider_delete_after_inactive": True, + "post_delete_app_absent": True, + "post_delete_provider_inventory_empty": True, + "github_registration_not_exercised": True, + } + payload["retirement_inactive_state"] = inactive_state + payload["classification"] = "SUPPORTED" + payload["oracleSatisfied"] = True + payload["detail"] = ( + "exact packaged provider CreateInstance realized the source-derived fixed " + "runner App profile on real nested TrueNAS; exact Compose was read back, " + "Get/List reconciled the created identity, the experiment App was moved " + "inactive through supported middleware, provider DeleteInstance retired it, " + "and no GitHub credential, JIT registration, private workload, physical " + "TrueNAS, or capacity promotion was exercised" + ) + except Exception as exc: + payload["detail"] = str(exc).replace(token, "") + finally: + try: + session.connect() + if app_name and query_app(session, app_name) is not None: + fallback_cleanup_used = True + try: + g2.delete_app_if_present(session, app_name, a.job_timeout) + except Exception as exc: + cleanup_errors.append( + f"fallback delete {app_name}: {type(exc).__name__}: {exc}" + ) + if app_name: + try: + if query_app(session, app_name) is not None: + cleanup_errors.append( + f"provider-created App remains after cleanup: {app_name}" + ) + except Exception as exc: + cleanup_errors.append( + f"verify App absence {app_name}: {type(exc).__name__}: {exc}" + ) + + if api_key_id is not None: + try: + deleted = session.call("api_key.delete", [api_key_id]) + if deleted is not True: + cleanup_errors.append("api_key.delete did not return true") + except Exception as exc: + cleanup_errors.append( + f"api_key.delete: {type(exc).__name__}: {exc}" + ) + api_key_value = None + + if original_ui_cert_id is not None and imported_cert_id is not None: + try: + session.call( + "system.general.update", + [{"ui_certificate": original_ui_cert_id}], + ) + try: + session.call("system.general.ui_restart", [0]) + except Exception: + pass + except Exception as exc: + cleanup_errors.append( + f"restore ui certificate: {type(exc).__name__}: {exc}" + ) + session.close() + time.sleep(5) + try: + session.connect() + except Exception as exc: + cleanup_errors.append( + f"reconnect after ui restore: {type(exc).__name__}: {exc}" + ) + + if imported_cert_id is not None and session.ws is not None: + try: + delete_result = session.call( + "certificate.delete", [imported_cert_id, False] + ) + if isinstance(delete_result, int): + session.wait_job( + delete_result, "certificate.delete", a.job_timeout + ) + elif delete_result is not True: + cleanup_errors.append( + f"certificate.delete unexpected result {delete_result!r}" + ) + except Exception as exc: + cleanup_errors.append( + f"certificate.delete: {type(exc).__name__}: {exc}" + ) + except Exception as exc: + cleanup_errors.append( + f"cleanup session: {type(exc).__name__}: {exc}" + ) + finally: + session.close() + + payload["cleanup"] = { + "attempted": True, + "provider_delete_succeeded": provider_delete_succeeded, + "fallback_cleanup_used": fallback_cleanup_used, + "errors": cleanup_errors, + "zero_unintended_residue": not cleanup_errors, + "secret_values_recorded": False, + "synthetic_token_recorded": False, + } + if cleanup_errors or fallback_cleanup_used: + payload["oracleSatisfied"] = False + payload["classification"] = "ORACLE_FAILURE" + reasons = [] + if fallback_cleanup_used: + reasons.append("provider retirement required fallback direct cleanup") + reasons.extend(cleanup_errors) + payload["detail"] = payload.get("detail", "") + "; cleanup: " + "; ".join(reasons) + + payload["elapsed_seconds"] = round(time.time() - started, 3) + pathlib.Path(a.out).write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + print(json.dumps(payload, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 4364e31b2493b6cbc92fe416275d33ad262ade8a Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:08:39 +0200 Subject: [PATCH 2/7] test: lock G3 provider CreateInstance boundary --- tests/test_truenas_garm_provider_g3.py | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 tests/test_truenas_garm_provider_g3.py diff --git a/tests/test_truenas_garm_provider_g3.py b/tests/test_truenas_garm_provider_g3.py new file mode 100644 index 000000000..adf85fe56 --- /dev/null +++ b/tests/test_truenas_garm_provider_g3.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "truenas_middleware_garm_provider_g3_probe.py" +SPEC = importlib.util.spec_from_file_location("garm_provider_g3_probe", SCRIPT) +assert SPEC and SPEC.loader +MOD = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(MOD) + + +class GarmProviderG3Tests(unittest.TestCase): + def fixture_bundle(self) -> dict: + return { + "schema": MOD.EXPECTED_FIXTURE_SCHEMA, + "provider_product_source": MOD.EXPECTED_PROVIDER_PRODUCT_SOURCE, + "producer_source": "a" * 40, + "controller_id": MOD.EXPECTED_CONTROLLER_ID, + "pool_id": MOD.EXPECTED_POOL_ID, + "bootstrap_template": { + "callback-url": "https://fixture.invalid/status", + "metadata-url": "https://fixture.invalid/metadata", + "instance-token": "placeholder", + }, + "expected_app_name": "garm-g3-controlle-g3-synthetic-runner", + "expected_compose_template": { + "services": { + "runner": { + "environment": { + "GARM_CALLBACK_URL": "https://fixture.invalid/status", + "GARM_METADATA_URL": "https://fixture.invalid/metadata", + "GARM_INSTANCE_TOKEN": "placeholder", + } + } + } + }, + "run_local_substitutions": MOD.EXPECTED_SUBSTITUTIONS, + } + + def test_fixture_bundle_binds_product_producer_and_substitution_allowlist(self): + with tempfile.TemporaryDirectory() as td: + root = Path(td) + (root / "garm-provider-g3-fixture.json").write_text( + json.dumps(self.fixture_bundle()), encoding="utf-8" + ) + loaded = MOD.load_bundle(root, "a" * 40) + self.assertEqual( + loaded["provider_product_source"], + MOD.EXPECTED_PROVIDER_PRODUCT_SOURCE, + ) + + with self.assertRaisesRegex(RuntimeError, "producer source drifted"): + MOD.load_bundle(root, "b" * 40) + + bad = self.fixture_bundle() + bad["run_local_substitutions"] = bad["run_local_substitutions"][:-1] + (root / "garm-provider-g3-fixture.json").write_text( + json.dumps(bad), encoding="utf-8" + ) + with self.assertRaisesRegex(RuntimeError, "substitution allowlist drifted"): + MOD.load_bundle(root, "a" * 40) + + def test_lower_fixture_changes_only_callback_metadata_and_synthetic_token(self): + bundle = self.fixture_bundle() + callback = "https://example.invalid/anything/status" + metadata = "https://example.invalid/anything/metadata" + token = "synthetic-token" + bootstrap, expected = MOD.lower_fixture(bundle, callback, metadata, token) + + self.assertEqual(bootstrap["callback-url"], callback) + self.assertEqual(bootstrap["metadata-url"], metadata) + self.assertEqual(bootstrap["instance-token"], token) + env = expected["services"]["runner"]["environment"] + self.assertEqual(env["GARM_CALLBACK_URL"], callback) + self.assertEqual(env["GARM_METADATA_URL"], metadata) + self.assertEqual(env["GARM_INSTANCE_TOKEN"], token) + + original = self.fixture_bundle() + self.assertEqual( + original["bootstrap_template"]["callback-url"], + "https://fixture.invalid/status", + ) + + def test_boundary_is_create_readback_retire_not_github_execution(self): + text = SCRIPT.read_text(encoding="utf-8") + for required in ( + '"CreateInstance"', + '"GetInstance"', + '"ListInstances"', + '"DeleteInstance"', + '"provider_create_exercised": False', + '"provider_delete_exercised": False', + '"github_credentials_present": False', + '"github_jit_registration_exercised": False', + '"private_repository_execution": False', + '"physical_truenas_mutation": False', + '"capacity_promotion": False', + '"callback_host_gateway": False', + '"fallback_cleanup_used": fallback_cleanup_used', + '"provider_delete_succeeded": provider_delete_succeeded', + ): + self.assertIn(required, text) + self.assertNotIn("GITHUB_TOKEN", text) + self.assertNotIn("GH_TOKEN", text) + self.assertNotIn("PRIVATE_KEY", text) + + def test_exact_compose_is_compared_before_retirement(self): + text = SCRIPT.read_text(encoding="utf-8") + readback = text.index('session.call("app.config", [app_name])') + compare = text.index("provider-created exact Compose read-back drifted") + inactive = text.index("ensure_inactive(session, app_name") + delete = text.index('"DeleteInstance", instance_id=app_name') + self.assertLess(readback, compare) + self.assertLess(compare, inactive) + self.assertLess(inactive, delete) + + def test_transport_reuses_verified_g2_helper_tuple(self): + text = SCRIPT.read_text(encoding="utf-8") + self.assertIn("g2.wait_verified_https", text) + self.assertIn('"SSL_CERT_FILE": str(ca_path)', text) + self.assertIn('"TRUENAS_API_KEY": api_key', text) + self.assertIn("g2.EXPECTED_APPLIANCE != EXPECTED_APPLIANCE", text) + self.assertIn( + "g2.EXPECTED_PROVIDER_BINARY_SHA256 != EXPECTED_PROVIDER_BINARY_SHA256", + text, + ) + + +if __name__ == "__main__": + unittest.main() From aff1b7f8e13eaa14db51127a892e09ea93f7cdb6 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:09:04 +0200 Subject: [PATCH 3/7] rdte: route source-exact G3 provider fixture --- .github/workflows/gha-kvm-system-rdte.yml | 33 +++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gha-kvm-system-rdte.yml b/.github/workflows/gha-kvm-system-rdte.yml index d237d5d12..106258a2f 100644 --- a/.github/workflows/gha-kvm-system-rdte.yml +++ b/.github/workflows/gha-kvm-system-rdte.yml @@ -47,6 +47,7 @@ on: - wow-sidecar - garm - garm-provider-g2 + - garm-provider-g3 permissions: contents: read @@ -107,7 +108,7 @@ jobs: scripts/gha_kvm_proxmox_rdte.sh) proxmox=true ;; - config/truenas-rdte-targets.json|scripts/truenas_rdte_target.py|scripts/gha_kvm_truenas_rdte.sh|scripts/truenas_installer_rpc_probe.py|scripts/truenas_installer_rpc_install.py|scripts/truenas_middleware_ddp_probe.py|scripts/truenas_middleware_pool_probe.py|scripts/truenas_middleware_app_probe.py|scripts/truenas_middleware_app_lifecycle_probe.py|scripts/truenas_middleware_litellm_t6_probe.py|scripts/truenas_middleware_wow_sidecar_t6_probe.py|scripts/truenas_middleware_garm_t6_probe.py|scripts/truenas_middleware_garm_provider_g2_probe.py) + config/truenas-rdte-targets.json|scripts/truenas_rdte_target.py|scripts/gha_kvm_truenas_rdte.sh|scripts/truenas_installer_rpc_probe.py|scripts/truenas_installer_rpc_install.py|scripts/truenas_middleware_ddp_probe.py|scripts/truenas_middleware_pool_probe.py|scripts/truenas_middleware_app_probe.py|scripts/truenas_middleware_app_lifecycle_probe.py|scripts/truenas_middleware_litellm_t6_probe.py|scripts/truenas_middleware_wow_sidecar_t6_probe.py|scripts/truenas_middleware_garm_t6_probe.py|scripts/truenas_middleware_garm_provider_g2_probe.py|scripts/truenas_middleware_garm_provider_g3_probe.py) truenas=true ;; scripts/gha_qemu_t3_topology_probe.py|.github/workflows/gha-kvm-system-rdte.yml) @@ -118,7 +119,7 @@ jobs: if [[ "$run_request_changed" == "true" ]]; then truenas_version="$(jq -er '.version | select(type == "string" and length > 0)' "$RUN_REQUEST_PATH")" truenas_rung="$(jq -er '.rung | select(. == "t0" or . == "t1" or . == "t2" or . == "t3" or . == "t4" or . == "t5" or . == "t6")' "$RUN_REQUEST_PATH")" - truenas_t6_product="$(jq -er '(.product // "litellm") | select(. == "litellm" or . == "wow-sidecar" or . == "garm" or . == "garm-provider-g2")' "$RUN_REQUEST_PATH")" + truenas_t6_product="$(jq -er '(.product // "litellm") | select(. == "litellm" or . == "wow-sidecar" or . == "garm" or . == "garm-provider-g2" or . == "garm-provider-g3")' "$RUN_REQUEST_PATH")" jq -e '.schema == "gha-kvm-truenas-run-request/v1"' "$RUN_REQUEST_PATH" >/dev/null python3 scripts/truenas_rdte_target.py \ --registry config/truenas-rdte-targets.json \ @@ -145,7 +146,7 @@ jobs: with: persist-credentials: false - name: Validate public-safe contract - run: python3 -m unittest -v tests/test_gha_kvm_system_rdte.py tests/test_truenas_future_protocols.py tests/test_truenas_t6_litellm_control.py tests/test_truenas_t6_wow_sidecar_control.py tests/test_truenas_t6_garm_control.py tests/test_truenas_garm_provider_g2.py tests/test_truenas_rdte_target.py + run: python3 -m unittest -v tests/test_gha_kvm_system_rdte.py tests/test_truenas_future_protocols.py tests/test_truenas_t6_litellm_control.py tests/test_truenas_t6_wow_sidecar_control.py tests/test_truenas_t6_garm_control.py tests/test_truenas_garm_provider_g2.py tests/test_truenas_garm_provider_g3.py tests/test_truenas_rdte_target.py foundry-t6-control: needs: [changes, contract] @@ -183,6 +184,15 @@ jobs: permissions: contents: read + provider-g3-fixture: + needs: [changes, contract] + if: needs.contract.result == 'success' && needs.changes.outputs.truenas == 'true' && needs.changes.outputs.truenas_rung == 't6' && needs.changes.outputs.truenas_t6_product == 'garm-provider-g3' + uses: SemperSupra/garm-provider-truenas/.github/workflows/export-g3-nested-fixture.yml@e92d4024e6e60c0e2f8bcf5301c11fccb14eaaab + with: + provider_ref: e92d4024e6e60c0e2f8bcf5301c11fccb14eaaab + permissions: + contents: read + qemu-t3-topology: needs: [changes, contract] if: needs.contract.result == 'success' && needs.changes.outputs.qemu_topology == 'true' @@ -264,7 +274,7 @@ jobs: retention-days: 7 truenas-26-beta3: - needs: [changes, contract, proxmox-9-2, foundry-t6-control, foundry-wow-t6-control, foundry-garm-t6-control, provider-g2-fixtures] + needs: [changes, contract, proxmox-9-2, foundry-t6-control, foundry-wow-t6-control, foundry-garm-t6-control, provider-g2-fixtures, provider-g3-fixture] if: >- always() && needs.contract.result == 'success' && @@ -275,7 +285,8 @@ jobs: (needs.changes.outputs.truenas_t6_product == 'litellm' && needs['foundry-t6-control'].result == 'success') || (needs.changes.outputs.truenas_t6_product == 'wow-sidecar' && needs['foundry-wow-t6-control'].result == 'success') || (needs.changes.outputs.truenas_t6_product == 'garm' && needs['foundry-garm-t6-control'].result == 'success') || - (needs.changes.outputs.truenas_t6_product == 'garm-provider-g2' && needs['provider-g2-fixtures'].result == 'success') + (needs.changes.outputs.truenas_t6_product == 'garm-provider-g2' && needs['provider-g2-fixtures'].result == 'success') || + (needs.changes.outputs.truenas_t6_product == 'garm-provider-g3' && needs['provider-g3-fixture'].result == 'success') ) runs-on: ubuntu-26.04 timeout-minutes: 50 @@ -307,6 +318,12 @@ jobs: with: name: garm-provider-truenas-g2-fixtures path: ${{ runner.temp }}/gha-kvm-system-rdte/g2-fixtures + - name: Download exact GARM provider G3 fixture + if: needs.changes.outputs.truenas_rung == 't6' && needs.changes.outputs.truenas_t6_product == 'garm-provider-g3' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c + with: + name: garm-provider-truenas-g3-fixture + path: ${{ runner.temp }}/gha-kvm-system-rdte/g3-fixture - name: Ensure bounded QEMU/KVM prerequisites run: | set -euo pipefail @@ -330,6 +347,12 @@ jobs: --g2-fixture-dir "$RUNNER_TEMP/gha-kvm-system-rdte/g2-fixtures" --g2-fixture-producer "1667aa73e514c278e221082ac33f2f10a4141eef" ) + elif [[ "${{ needs.changes.outputs.truenas_t6_product }}" == "garm-provider-g3" ]]; then + args+=( + --t6-product "garm-provider-g3" + --g3-fixture-dir "$RUNNER_TEMP/gha-kvm-system-rdte/g3-fixture" + --g3-fixture-producer "e92d4024e6e60c0e2f8bcf5301c11fccb14eaaab" + ) else if [[ "${{ needs.changes.outputs.truenas_t6_product }}" == "garm" ]]; then foundry_commit="7fd0d2474248b61cf8e290fc80f40b761cea5e98" From 833d20e2ebfd2cfd202ab501ace6f8de87e0bb24 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:09:36 +0200 Subject: [PATCH 4/7] rdte: add G3 provider create consumer path --- scripts/gha_kvm_truenas_rdte.sh | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/scripts/gha_kvm_truenas_rdte.sh b/scripts/gha_kvm_truenas_rdte.sh index d052cca47..cc0f5f52a 100755 --- a/scripts/gha_kvm_truenas_rdte.sh +++ b/scripts/gha_kvm_truenas_rdte.sh @@ -17,7 +17,7 @@ MIN_HOST_MEM_KIB=$((11 * 1024 * 1024)) MIN_HOST_FREE_KIB=$((28 * 1024 * 1024)) usage() { - echo "Usage: gha_kvm_truenas_rdte.sh --out RECEIPT [--state-dir DIR] [--target-version VERSION] [--rung t0|t1|t2|t3|t4|t5|t6] [--t6-product litellm|wow-sidecar|garm|garm-provider-g2] [--foundry-control-dir DIR] [--foundry-commit SHA] [--g2-fixture-dir DIR] [--g2-fixture-producer SHA]" + echo "Usage: gha_kvm_truenas_rdte.sh --out RECEIPT [--state-dir DIR] [--target-version VERSION] [--rung t0|t1|t2|t3|t4|t5|t6] [--t6-product litellm|wow-sidecar|garm|garm-provider-g2|garm-provider-g3] [--foundry-control-dir DIR] [--foundry-commit SHA] [--g2-fixture-dir DIR] [--g2-fixture-producer SHA] [--g3-fixture-dir DIR] [--g3-fixture-producer SHA]" } OUT="" @@ -28,6 +28,8 @@ FOUNDRY_CONTROL_DIR="" FOUNDRY_COMMIT="" G2_FIXTURE_DIR="" G2_FIXTURE_PRODUCER="" +G3_FIXTURE_DIR="" +G3_FIXTURE_PRODUCER="" while [[ $# -gt 0 ]]; do case "$1" in --out) OUT="$2"; shift 2 ;; @@ -39,6 +41,8 @@ while [[ $# -gt 0 ]]; do --foundry-commit) FOUNDRY_COMMIT="$2"; shift 2 ;; --g2-fixture-dir) G2_FIXTURE_DIR="$2"; shift 2 ;; --g2-fixture-producer) G2_FIXTURE_PRODUCER="$2"; shift 2 ;; + --g3-fixture-dir) G3_FIXTURE_DIR="$2"; shift 2 ;; + --g3-fixture-producer) G3_FIXTURE_PRODUCER="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;; esac @@ -51,11 +55,15 @@ eval "$TARGET_ENV" [[ "$RUNG" == "t0" || "$RUNG" == "t1" || "$RUNG" == "t2" || "$RUNG" == "t3" || "$RUNG" == "t4" || "$RUNG" == "t5" || "$RUNG" == "t6" ]] || { echo "rung must be t0, t1, t2, t3, t4, t5, or t6" >&2; exit 2; } if [[ "$RUNG" == "t6" ]]; then [[ "$VERSION" == "26.0.0-BETA.3" ]] || { echo "T6 is currently admitted only for exact TrueNAS 26.0.0-BETA.3; use Foundry F1-F5 for other targets" >&2; exit 2; } - [[ "$T6_PRODUCT" == "litellm" || "$T6_PRODUCT" == "wow-sidecar" || "$T6_PRODUCT" == "garm" || "$T6_PRODUCT" == "garm-provider-g2" ]] || { echo "unsupported T6 product: $T6_PRODUCT" >&2; exit 2; } + [[ "$T6_PRODUCT" == "litellm" || "$T6_PRODUCT" == "wow-sidecar" || "$T6_PRODUCT" == "garm" || "$T6_PRODUCT" == "garm-provider-g2" || "$T6_PRODUCT" == "garm-provider-g3" ]] || { echo "unsupported T6 product: $T6_PRODUCT" >&2; exit 2; } if [[ "$T6_PRODUCT" == "garm-provider-g2" ]]; then [[ -n "$G2_FIXTURE_DIR" && -d "$G2_FIXTURE_DIR" ]] || { echo "garm-provider-g2 requires --g2-fixture-dir" >&2; exit 2; } [[ "$G2_FIXTURE_PRODUCER" =~ ^[0-9a-f]{40}$ ]] || { echo "garm-provider-g2 requires exact --g2-fixture-producer SHA" >&2; exit 2; } G2_FIXTURE_DIR="$(realpath "$G2_FIXTURE_DIR")" + elif [[ "$T6_PRODUCT" == "garm-provider-g3" ]]; then + [[ -n "$G3_FIXTURE_DIR" && -d "$G3_FIXTURE_DIR" ]] || { echo "garm-provider-g3 requires --g3-fixture-dir" >&2; exit 2; } + [[ "$G3_FIXTURE_PRODUCER" =~ ^[0-9a-f]{40}$ ]] || { echo "garm-provider-g3 requires exact --g3-fixture-producer SHA" >&2; exit 2; } + G3_FIXTURE_DIR="$(realpath "$G3_FIXTURE_DIR")" else [[ -n "$FOUNDRY_CONTROL_DIR" && -d "$FOUNDRY_CONTROL_DIR" ]] || { echo "t6 requires --foundry-control-dir" >&2; exit 2; } [[ "$FOUNDRY_COMMIT" =~ ^[0-9a-f]{40}$ ]] || { echo "t6 requires exact --foundry-commit SHA" >&2; exit 2; } @@ -129,6 +137,8 @@ payload = { "app": ( {"name": "garm-provider-g2-fixtures", "image": "ghcr.io/actions/actions-runner:2.336.0@sha256:0cfdcc701ce933c6d243c6b0b2da767366dc9f2e99961d4c3754b0b78084cdda"} if os.environ.get("R_RUNG") == "t6" and os.environ.get("R_T6_PRODUCT") == "garm-provider-g2" + else {"name": "garm-provider-g3-runner", "image": "ghcr.io/actions/actions-runner:2.336.0@sha256:0cfdcc701ce933c6d243c6b0b2da767366dc9f2e99961d4c3754b0b78084cdda"} + if os.environ.get("R_RUNG") == "t6" and os.environ.get("R_T6_PRODUCT") == "garm-provider-g3" else {"name": "rdte-t6-garm", "image": "ghcr.io/sempersupra/garm-appliance@sha256:1af67841ddd4589e3798dcda8be49230565c849d07ab57fd05899432dcdabca9"} if os.environ.get("R_RUNG") == "t6" and os.environ.get("R_T6_PRODUCT") == "garm" else {"name": "rdte-t6-wow-sidecar", "image": "ghcr.io/sempersupra/wow-sidecar@sha256:6b700ce7ba5ae44116b240ccbb54fb3b60dc952a9b4072ca1314b6f311bc5376"} @@ -223,6 +233,11 @@ if [[ "$RUNG" == "t2" || "$RUNG" == "t3" || "$RUNG" == "t4" || "$RUNG" == "t5" | fail_evidence HARNESS_FAILURE preflight "missing TrueNAS GARM provider G2 client" command -v docker >/dev/null 2>&1 || fail_evidence ENVIRONMENT_FAILURE preflight "missing prerequisite: docker" command -v openssl >/dev/null 2>&1 || fail_evidence ENVIRONMENT_FAILURE preflight "missing prerequisite: openssl" + elif [[ "$T6_PRODUCT" == "garm-provider-g3" ]]; then + [[ -f "$SCRIPT_DIR/truenas_middleware_garm_provider_g3_probe.py" ]] || + fail_evidence HARNESS_FAILURE preflight "missing TrueNAS GARM provider G3 client" + command -v docker >/dev/null 2>&1 || fail_evidence ENVIRONMENT_FAILURE preflight "missing prerequisite: docker" + command -v openssl >/dev/null 2>&1 || fail_evidence ENVIRONMENT_FAILURE preflight "missing prerequisite: openssl" elif [[ "$T6_PRODUCT" == "garm" ]]; then [[ -f "$SCRIPT_DIR/truenas_middleware_garm_t6_probe.py" ]] || fail_evidence HARNESS_FAILURE preflight "missing TrueNAS T6 GARM control client" @@ -638,6 +653,13 @@ if [[ "$T6_PRODUCT" == "garm-provider-g2" ]]; then --fixture-dir "$G2_FIXTURE_DIR" \ --fixture-producer-commit "$G2_FIXTURE_PRODUCER" \ --out "$FOUNDRY_OUT" --timeout 8 --job-timeout 300 >/dev/null 2>&1 || true +elif [[ "$T6_PRODUCT" == "garm-provider-g3" ]]; then + python3 "$SCRIPT_DIR/truenas_middleware_garm_provider_g3_probe.py" \ + --host 127.0.0.1 --http-port "$HTTP_PORT" --https-port "$HTTPS_PORT" \ + --password-file "$PASSWORD_FILE" \ + --fixture-dir "$G3_FIXTURE_DIR" \ + --fixture-producer-commit "$G3_FIXTURE_PRODUCER" \ + --out "$FOUNDRY_OUT" --timeout 8 --job-timeout 300 >/dev/null 2>&1 || true elif [[ "$T6_PRODUCT" == "garm" ]]; then python3 "$SCRIPT_DIR/truenas_middleware_garm_t6_probe.py" \ --host 127.0.0.1 --port "$MIDDLEWARE_PORT" --service-port "$GARM_HOST_PORT" \ @@ -676,6 +698,8 @@ PY if [[ "$T6_PRODUCT" == "garm-provider-g2" ]]; then write_receipt SUPPORTED true foundry-materialization "exact packaged GARM TrueNAS provider passed verified WSS/API-key transport, valid local adoption, foreign exclusion, managed-drift fail-closed behavior, fresh-process readoption, and zero-residue cleanup; provider create/delete and GitHub/JIT intentionally not exercised" +elif [[ "$T6_PRODUCT" == "garm-provider-g3" ]]; then + write_receipt SUPPORTED true foundry-materialization "exact packaged GARM TrueNAS provider CreateInstance realized the source-derived fixed runner App profile, exact Compose read-back and provider Get/List reconciliation passed, supported middleware moved the experiment App inactive, provider DeleteInstance retired it, and GitHub/JIT/private workload/physical/capacity claims remained unexercised" elif [[ "$T6_PRODUCT" == "garm" ]]; then write_receipt SUPPORTED true foundry-materialization "exact Foundry-exported GARM controller control passed exact appliance and secret-normalized config read-back, persistent state, external HTTPS, restart persistence, and zero-residue cleanup; GitHub/JIT registration intentionally not exercised" elif [[ "$T6_PRODUCT" == "wow-sidecar" ]]; then From dee947dafcb13db10cd5997629fe0ec9a4f57fc1 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:10:11 +0200 Subject: [PATCH 5/7] test: admit G3 provider consumer authority --- tests/test_truenas_rdte_target.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_truenas_rdte_target.py b/tests/test_truenas_rdte_target.py index 31fd49bd8..abe415f9e 100755 --- a/tests/test_truenas_rdte_target.py +++ b/tests/test_truenas_rdte_target.py @@ -49,15 +49,15 @@ def test_run_request_is_exact_registered_target(self): target=self.targets[request["version"]] self.assertEqual(request["authority_issue"], target["authority_issue"]) if request["rung"] == "t6": - self.assertIn(request.get("product", "litellm"), {"litellm","wow-sidecar","garm","garm-provider-g2"}) + self.assertIn(request.get("product", "litellm"), {"litellm","wow-sidecar","garm","garm-provider-g2","garm-provider-g3"}) if request.get("product") == "wow-sidecar": self.assertEqual(request.get("consumer_authority_issue"), 276) if request.get("product") == "garm": self.assertEqual(request.get("consumer_authority_issue"), 277) if request.get("product") == "garm-provider-g2": self.assertEqual(request.get("consumer_authority_issue"), 39) - if request.get("product") == "garm-provider-g2": - self.assertEqual(request.get("consumer_authority_issue"), 39) + if request.get("product") == "garm-provider-g3": + self.assertEqual(request.get("consumer_authority_issue"), 40) self.assertNotIn("latest", request["version"].lower()) self.assertNotIn("nightly", request["version"].lower()) From d8b23886142d192b898793adb0334bcdce0bd424 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:12:11 +0200 Subject: [PATCH 6/7] rdte: preflight G3 synthetic HTTPS fixture --- ...uenas_middleware_garm_provider_g3_probe.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/scripts/truenas_middleware_garm_provider_g3_probe.py b/scripts/truenas_middleware_garm_provider_g3_probe.py index c51e6b8e6..cff8ecbfb 100644 --- a/scripts/truenas_middleware_garm_provider_g3_probe.py +++ b/scripts/truenas_middleware_garm_provider_g3_probe.py @@ -11,6 +11,7 @@ import subprocess import tempfile import time +import urllib.request import truenas_middleware_garm_provider_g2_probe as g2 @@ -37,6 +38,54 @@ ] +class FixtureEnvironmentError(RuntimeError): + pass + + +def preflight_public_fixture(callback_url: str, metadata_url: str, timeout: float) -> None: + payload = b'{"status":"probe","message":"public synthetic G3 fixture preflight"}' + request = urllib.request.Request( + callback_url, + data=payload, + method="POST", + headers={ + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": "SemperSupra-G3-RDTE/1", + }, + ) + try: + with urllib.request.urlopen(request, timeout=timeout) as response: + if not (200 <= response.status < 300): + raise FixtureEnvironmentError( + f"synthetic callback preflight returned HTTP {response.status}" + ) + response.read(4096) + + metadata_probe = metadata_url.rstrip("/") + "/credentials/runner" + request = urllib.request.Request( + metadata_probe, + method="GET", + headers={ + "Accept": "application/json", + "User-Agent": "SemperSupra-G3-RDTE/1", + }, + ) + with urllib.request.urlopen(request, timeout=timeout) as response: + body = response.read(4096) + if not (200 <= response.status < 300) or not body: + raise FixtureEnvironmentError( + "synthetic metadata preflight did not return non-empty HTTP 2xx" + ) + except FixtureEnvironmentError: + raise + except Exception as exc: + raise FixtureEnvironmentError( + f"public synthetic callback/metadata fixture unavailable: {type(exc).__name__}: {exc}" + ) from exc + + + def load_bundle(directory: pathlib.Path, expected_producer: str) -> dict: path = directory / "garm-provider-g3-fixture.json" bundle = json.loads(path.read_text(encoding="utf-8")) @@ -241,6 +290,8 @@ def main() -> int: try: bundle = load_bundle(a.fixture_dir, a.fixture_producer_commit) payload["fixture_producer_source"] = bundle["producer_source"] + preflight_public_fixture(a.callback_url, a.metadata_url, a.timeout) + payload["synthetic_fixture_preflight"] = True payload["fixture_bundle_sha256"] = g2.sha256_file( a.fixture_dir / "garm-provider-g3-fixture.json" ) @@ -427,6 +478,9 @@ def main() -> int: "and no GitHub credential, JIT registration, private workload, physical " "TrueNAS, or capacity promotion was exercised" ) + except FixtureEnvironmentError as exc: + payload["classification"] = "ENVIRONMENT_FAILURE" + payload["detail"] = str(exc).replace(token, "") except Exception as exc: payload["detail"] = str(exc).replace(token, "") finally: From 32696bf993487799b942b18e36ea55f390ed59f0 Mon Sep 17 00:00:00 2001 From: "Mark E. DeYoung" <1854350+mark-e-deyoung@users.noreply.github.com> Date: Thu, 1 Oct 2026 06:12:24 +0200 Subject: [PATCH 7/7] test: lock G3 fixture preflight boundary --- tests/test_truenas_garm_provider_g3.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_truenas_garm_provider_g3.py b/tests/test_truenas_garm_provider_g3.py index adf85fe56..5fbef0046 100644 --- a/tests/test_truenas_garm_provider_g3.py +++ b/tests/test_truenas_garm_provider_g3.py @@ -119,6 +119,17 @@ def test_exact_compose_is_compared_before_retirement(self): self.assertLess(compare, inactive) self.assertLess(inactive, delete) + def test_synthetic_fixture_preflight_sends_no_provider_token(self): + text = SCRIPT.read_text(encoding="utf-8") + block = text.split("def preflight_public_fixture", 1)[1].split( + "def load_bundle", 1 + )[0] + self.assertIn('method="POST"', block) + self.assertIn('method="GET"', block) + self.assertNotIn("Authorization", block) + self.assertIn('"ENVIRONMENT_FAILURE"', text) + self.assertIn('"synthetic_fixture_preflight"', text) + def test_transport_reuses_verified_g2_helper_tuple(self): text = SCRIPT.read_text(encoding="utf-8") self.assertIn("g2.wait_verified_https", text)