[AZ-263] Bootstrap: repo skeleton + Docker + CI + Alembic + Tier-1 tests

Implements the AZ-263 / E-BOOT initial structure task:

- Python src/-layout package `gps_denied_onboard/` with per-component
  interface stubs (14 components), type-only DTOs under `_types/`,
  shared helpers under `helpers/` (R14 LightGlue ownership), structured
  JSON logging, runtime composition root with env-var fail-fast gate,
  healthcheck module shared by Docker and CI smoke.
- CMake top-level + `cmake/{build_options,dependencies,strategies}.cmake`
  with the BUILD_* per-binary flags (ADR-002) and pinned external git
  refs for OKVIS2 / VINS-Mono / GTSAM / FAISS / OpenCV >=4.12.0.
- Three Dockerfiles (companion-tier1, operator-tooling,
  mock-suite-sat-service) + two compose files (dev + Tier-1 test).
- Four GitHub Actions workflows: ci.yml (lint/unit/integration/dual
  binary build/SBOM diff/security), ci-tier2.yml (self-hosted Jetson
  AC-bound NFTs), release.yml, cve-rescan.yml.
- Two CI gate scripts: `ci/sbom_diff.py` (deployment SBOM subset +
  R02 exclusion), `ci/opencv_pin_gate.py` (>=4.12.0 enforcement,
  D-CROSS-CVE-1).
- Alembic-driven Postgres 16 initial migration `0001_initial.py`
  mirroring satellite-provider tiles + flights + sector_classifications
  + manifests + engine_cache_entries (data_model.md s 2).
- Tier-1 test scaffolding: 95 passing unit tests covering every AC,
  per-component smoke tests, structured logging JSON output check,
  env-var gate check, healthcheck import check. Two CI-gated tests
  (cmake configure, actionlint) skip locally with explicit reasons.
- Batch report + code review report under `_docs/03_implementation/`.

Verdict: PASS_WITH_WARNINGS (two Low findings, both informational).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 01:00:28 +03:00
parent 880eabcb3f
commit b12db61444
168 changed files with 3688 additions and 3 deletions
View File
+13
View File
@@ -0,0 +1,13 @@
"""C10 CacheProvisioner smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c10_provisioning import (
CacheProvisioner,
EngineCacheEntry,
Manifest,
)
for sym in (CacheProvisioner, Manifest, EngineCacheEntry):
assert sym is not None
+12
View File
@@ -0,0 +1,12 @@
"""C11 TileManager smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c11_tile_manager import (
TileDownloader,
TileUploader,
)
assert TileDownloader is not None
assert TileUploader is not None
@@ -0,0 +1,12 @@
"""C12 OperatorTooling smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c12_operator_tooling import (
CacheBuildWorkflow,
OperatorReLocService,
)
assert CacheBuildWorkflow is not None
assert OperatorReLocService is not None
View File
+8
View File
@@ -0,0 +1,8 @@
"""C13 FDR Writer smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c13_fdr import FdrWriter
assert FdrWriter is not None
View File
+9
View File
@@ -0,0 +1,9 @@
"""C1 VIO smoke test — AZ-263 AC-9: verify the component interface is importable."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c1_vio import VioOutput, VioStrategy
assert VioStrategy is not None
assert VioOutput is not None
View File
+9
View File
@@ -0,0 +1,9 @@
"""C2.5 Rerank smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c2_5_rerank import RerankResult, RerankStrategy
assert RerankStrategy is not None
assert RerankResult is not None
View File
+10
View File
@@ -0,0 +1,10 @@
"""C2 VPR smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c2_vpr import VprQuery, VprResult, VprStrategy
assert VprStrategy is not None
assert VprQuery is not None
assert VprResult is not None
View File
+8
View File
@@ -0,0 +1,8 @@
"""C3.5 AdHoP smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c3_5_adhop import AdHoPRefinementStrategy
assert AdHoPRefinementStrategy is not None
View File
+9
View File
@@ -0,0 +1,9 @@
"""C3 Cross-Domain Matcher smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c3_matcher import CrossDomainMatcher, MatchResult
assert CrossDomainMatcher is not None
assert MatchResult is not None
View File
+14
View File
@@ -0,0 +1,14 @@
"""C4 PoseEstimator smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c4_pose import (
EstimatorOutput,
PoseEstimate,
PoseEstimator,
)
assert PoseEstimator is not None
assert PoseEstimate is not None
assert EstimatorOutput is not None
View File
+14
View File
@@ -0,0 +1,14 @@
"""C5 StateEstimator smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c5_state import (
EstimatorHealth,
EstimatorOutput,
StateEstimator,
)
assert StateEstimator is not None
assert EstimatorOutput is not None
assert EstimatorHealth is not None
+18
View File
@@ -0,0 +1,18 @@
"""C6 TileCache smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c6_tile_cache import (
DescriptorIndex,
SectorClassification,
Tile,
TileQualityMetadata,
TileRecord,
TileStore,
)
assert TileStore is not None
assert DescriptorIndex is not None
for cls in (Tile, TileQualityMetadata, TileRecord, SectorClassification):
assert cls is not None
View File
+12
View File
@@ -0,0 +1,12 @@
"""C7 InferenceRuntime smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c7_inference import (
EngineCacheEntry,
InferenceRuntime,
)
assert InferenceRuntime is not None
assert EngineCacheEntry is not None
+14
View File
@@ -0,0 +1,14 @@
"""C8 FC Adapter smoke test — AC-9."""
def test_interface_importable() -> None:
# Assert
from gps_denied_onboard.components.c8_fc_adapter import (
EmittedExternalPosition,
FcAdapter,
GcsAdapter,
ReplaySink,
)
for sym in (FcAdapter, GcsAdapter, ReplaySink, EmittedExternalPosition):
assert sym is not None
+124
View File
@@ -0,0 +1,124 @@
"""AC-10: SBOM diff script + OpenCV pin gate exist and run on stub builds."""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
CI_DIR = REPO_ROOT / "ci"
def test_sbom_diff_pass_on_subset(tmp_path: Path) -> None:
# Arrange
research = tmp_path / "research_sbom.json"
deployment = tmp_path / "deployment_sbom.json"
research.write_text(
json.dumps(
[
{"name": "numpy", "version": "1.26.4"},
{"name": "scipy", "version": "1.11.3"},
{"name": "okvis2", "version": "0.1.0"},
]
)
)
deployment.write_text(
json.dumps(
[
{"name": "numpy", "version": "1.26.4"},
{"name": "okvis2", "version": "0.1.0"},
]
)
)
# Act
result = subprocess.run(
[
sys.executable,
str(CI_DIR / "sbom_diff.py"),
"--deployment",
str(deployment),
"--research",
str(research),
],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode == 0, f"sbom_diff stderr:\n{result.stderr}"
def test_sbom_diff_fails_on_forbidden_component(tmp_path: Path) -> None:
# Arrange — ADR-002 / R02: vins_mono must not appear in deployment SBOM
research = tmp_path / "research_sbom.json"
deployment = tmp_path / "deployment_sbom.json"
research.write_text(json.dumps([{"name": "vins_mono", "version": "0.1"}]))
deployment.write_text(json.dumps([{"name": "vins_mono", "version": "0.1"}]))
# Act
result = subprocess.run(
[
sys.executable,
str(CI_DIR / "sbom_diff.py"),
"--deployment",
str(deployment),
"--research",
str(research),
],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode != 0, (
"sbom_diff must fail when a research-only component appears in deployment"
)
def test_opencv_pin_gate_passes_on_412_minimum() -> None:
# Act
result = subprocess.run(
[
sys.executable,
str(CI_DIR / "opencv_pin_gate.py"),
"--pyproject",
str(REPO_ROOT / "pyproject.toml"),
],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode == 0, f"opencv_pin_gate stderr:\n{result.stderr}"
def test_opencv_pin_gate_fails_on_lower_version(tmp_path: Path) -> None:
# Arrange
bad_pyproject = tmp_path / "pyproject.toml"
bad_pyproject.write_text(
'[project]\nname = "x"\nversion = "0.1"\ndependencies = ["opencv-python>=4.10,<5"]\n'
)
# Act
result = subprocess.run(
[
sys.executable,
str(CI_DIR / "opencv_pin_gate.py"),
"--pyproject",
str(bad_pyproject),
],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode != 0, (
"opencv_pin_gate must reject `opencv-python>=4.10` (D-CROSS-CVE-1 ≥ 4.12.0)"
)
+140
View File
@@ -0,0 +1,140 @@
"""AC-1: project scaffolded matching the layout in AZ-263.
Validates folder/file presence + that `pyproject.toml` and the top-level
`CMakeLists.txt` + `cmake/{dependencies,build_options}.cmake` parse without
error. The CMake configure step is gated on `cmake` being on PATH; when it
isn't, the test skips with an explicit prerequisite reason.
"""
from __future__ import annotations
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
REQUIRED_PATHS: tuple[str, ...] = (
"pyproject.toml",
"CMakeLists.txt",
"cmake/dependencies.cmake",
"cmake/build_options.cmake",
"cmake/strategies.cmake",
".clang-format",
".clang-tidy",
".cmake-format.yaml",
".editorconfig",
".env.example",
".dockerignore",
".gitignore",
"README.md",
"docker/companion-tier1.Dockerfile",
"docker/operator-tooling.Dockerfile",
"docker/mock-suite-sat-service.Dockerfile",
"docker-compose.yml",
"docker-compose.test.yml",
".github/workflows/ci.yml",
".github/workflows/ci-tier2.yml",
".github/workflows/release.yml",
".github/workflows/cve-rescan.yml",
"ci/sbom_diff.py",
"ci/opencv_pin_gate.py",
"src/gps_denied_onboard/__init__.py",
"src/gps_denied_onboard/runtime_root.py",
"src/gps_denied_onboard/healthcheck.py",
"src/gps_denied_onboard/_types/__init__.py",
"src/gps_denied_onboard/helpers/__init__.py",
"src/gps_denied_onboard/logging/structured.py",
"src/gps_denied_onboard/config/loader.py",
"src/gps_denied_onboard/fdr_client/client.py",
"alembic.ini",
"db/migrations/env.py",
"db/migrations/versions/0001_initial.py",
"scripts/run-tests.sh",
"scripts/run-performance-tests.sh",
)
COMPONENT_DIRS: tuple[str, ...] = (
"c1_vio",
"c2_vpr",
"c2_5_rerank",
"c3_matcher",
"c3_5_adhop",
"c4_pose",
"c5_state",
"c6_tile_cache",
"c7_inference",
"c8_fc_adapter",
"c10_provisioning",
"c11_tile_manager",
"c12_operator_tooling",
"c13_fdr",
)
@pytest.mark.parametrize("rel_path", REQUIRED_PATHS)
def test_required_path_exists(rel_path: str) -> None:
# Assert
assert (REPO_ROOT / rel_path).exists(), f"missing required path: {rel_path}"
@pytest.mark.parametrize("component", COMPONENT_DIRS)
def test_component_has_interface_and_init(component: str) -> None:
# Assert
comp_root = REPO_ROOT / "src" / "gps_denied_onboard" / "components" / component
assert (comp_root / "__init__.py").exists(), f"missing __init__.py for {component}"
assert (comp_root / "interface.py").exists(), f"missing interface.py for {component}"
def test_pyproject_toml_parses() -> None:
# Arrange
if sys.version_info >= (3, 11):
import tomllib
else:
try:
import tomli as tomllib # type: ignore[no-redef]
except ImportError: # pragma: no cover - tomli installed in dev extras
pytest.skip("tomli/tomllib not available")
# Act
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text())
# Assert
assert data["project"]["name"] == "gps-denied-onboard"
assert any(dep.startswith("opencv-python") for dep in data["project"]["dependencies"]), (
"opencv-python pin must be in dependencies"
)
def test_cmake_files_configure() -> None:
# Arrange
cmake = shutil.which("cmake")
if cmake is None:
pytest.skip("cmake not on PATH; CI image installs cmake (Tier-1 ci.yml)")
build_dir = REPO_ROOT / "build" / "ac1_smoke"
build_dir.mkdir(parents=True, exist_ok=True)
# Act
result = subprocess.run(
[
cmake,
"-S",
str(REPO_ROOT),
"-B",
str(build_dir),
"-DBUILD_TESTING=OFF",
],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode == 0, (
f"cmake configure failed:\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
)
+78
View File
@@ -0,0 +1,78 @@
"""AC-3: docker-compose.yml and docker-compose.test.yml are valid.
YAML syntactic validity always runs. The `docker compose ... config --quiet`
shape validation requires the Docker daemon and the v2 plugin; when those are
not present, that test skips with the prerequisite reason.
"""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
import pytest
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
COMPOSE_FILES = (
REPO_ROOT / "docker-compose.yml",
REPO_ROOT / "docker-compose.test.yml",
)
@pytest.mark.parametrize("compose_path", COMPOSE_FILES)
def test_compose_yaml_parses(compose_path: Path) -> None:
# Act
parsed = yaml.safe_load(compose_path.read_text())
# Assert
assert isinstance(parsed, dict), f"{compose_path.name} must parse to a mapping"
assert "services" in parsed, f"{compose_path.name} must declare a services block"
def test_compose_yml_declares_required_services() -> None:
# Arrange
data = yaml.safe_load((REPO_ROOT / "docker-compose.yml").read_text())
services = data["services"]
# Assert
for required in ("companion", "operator-tooling", "mock-sat", "db"):
assert required in services, f"docker-compose.yml missing service: {required}"
def test_compose_test_yml_extends_base() -> None:
# Arrange
data = yaml.safe_load((REPO_ROOT / "docker-compose.test.yml").read_text())
# Assert
assert "services" in data, "docker-compose.test.yml must declare services"
assert "e2e-runner" in data["services"], (
"docker-compose.test.yml must declare the e2e-runner sidecar"
)
@pytest.mark.parametrize("compose_path", COMPOSE_FILES)
def test_compose_config_quiet(compose_path: Path) -> None:
# Arrange
docker = shutil.which("docker")
if docker is None:
pytest.skip("docker CLI not on PATH; Tier-1 CI image installs Docker")
plugin_check = subprocess.run(
[docker, "compose", "version"], capture_output=True, text=True, check=False
)
if plugin_check.returncode != 0:
pytest.skip("docker compose v2 plugin unavailable; Tier-1 CI image installs it")
# Act
result = subprocess.run(
[docker, "compose", "-f", str(compose_path), "config", "--quiet"],
cwd=REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode == 0, (
f"docker compose config --quiet failed for {compose_path.name}:\n"
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
)
+68
View File
@@ -0,0 +1,68 @@
"""AC-4: GitHub Actions workflows under `.github/workflows/` are valid.
YAML syntactic validity + ADR-002 dual-binary build matrix check always run.
`actionlint` semantic validation runs only when the binary is on PATH; CI
installs it as a job step.
"""
from __future__ import annotations
import shutil
import subprocess
from pathlib import Path
import pytest
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
WORKFLOWS_DIR = REPO_ROOT / ".github" / "workflows"
WORKFLOWS = sorted(WORKFLOWS_DIR.glob("*.yml"))
def test_workflows_dir_populated() -> None:
# Assert
names = {p.name for p in WORKFLOWS}
assert {"ci.yml", "ci-tier2.yml", "release.yml", "cve-rescan.yml"} <= names
@pytest.mark.parametrize("workflow", WORKFLOWS, ids=[p.name for p in WORKFLOWS])
def test_workflow_yaml_parses(workflow: Path) -> None:
# Act
data = yaml.safe_load(workflow.read_text())
# Assert
assert isinstance(data, dict), f"{workflow.name} must parse to a mapping"
# GitHub Actions reserves `on` as a top-level key; PyYAML preserves it as a
# bool-style key, so also accept the bool key `True` produced by safe_load.
assert "on" in data or True in data, f"{workflow.name} missing trigger block"
assert data.get("jobs"), f"{workflow.name} must declare jobs"
def test_ci_yml_has_dual_binary_matrix() -> None:
"""ADR-002: deployment + research must both build in ci.yml."""
# Arrange
raw = (WORKFLOWS_DIR / "ci.yml").read_text()
# Assert
# Match the matrix dimension we care about without depending on YAML key order.
assert "deployment" in raw, "ci.yml matrix must include `deployment` kind"
assert "research" in raw, "ci.yml matrix must include `research` kind"
assert "matrix:" in raw, "ci.yml build job must use a strategy matrix"
def test_actionlint_passes() -> None:
# Arrange
actionlint = shutil.which("actionlint")
if actionlint is None:
pytest.skip("actionlint not on PATH; CI installs it before the lint job")
# Act
result = subprocess.run(
[actionlint, *(str(p) for p in WORKFLOWS)],
capture_output=True,
text=True,
check=False,
)
# Assert
assert result.returncode == 0, (
f"actionlint reported errors:\nstdout:\n{result.stdout}\nstderr:\n{result.stderr}"
)
+66
View File
@@ -0,0 +1,66 @@
"""AC-5: alembic head is `0001_initial` and schema matches data_model.md § 2.
The migration is verified by inspecting the head revision and the upgrade
script's table/column declarations. We do NOT spin up Postgres here — that's
covered by integration tests; this is a Tier-1 unit check that the migration
metadata is correctly wired.
"""
from __future__ import annotations
import os
import re
from pathlib import Path
import pytest
from alembic import script as alembic_script
from alembic.config import Config
REPO_ROOT = Path(__file__).resolve().parents[2]
MIGRATION_BODY = (REPO_ROOT / "db" / "migrations" / "versions" / "0001_initial.py").read_text()
def test_head_revision_is_0001_initial() -> None:
# Arrange
cwd = os.getcwd()
os.chdir(REPO_ROOT)
try:
cfg = Config(str(REPO_ROOT / "alembic.ini"))
sc = alembic_script.ScriptDirectory.from_config(cfg)
# Act
heads = sc.get_heads()
finally:
os.chdir(cwd)
# Assert
assert list(heads) == ["0001_initial"], f"unexpected heads: {heads}"
@pytest.mark.parametrize(
"table",
[
"tiles",
"flights",
"sector_classifications",
"manifests",
"engine_cache_entries",
],
)
def test_initial_migration_declares_table(table: str) -> None:
# Assert — tolerate multi-line `op.create_table(\n "<table>"` formatting.
pattern = re.compile(
r"create_table\(\s*['\"]" + re.escape(table) + r"['\"]",
re.DOTALL,
)
assert pattern.search(MIGRATION_BODY), f"0001_initial.py missing create_table for `{table}`"
def test_tiles_table_has_canonical_source_check() -> None:
"""Canonical onboard-only additive columns from data_model.md § 2.1.1."""
# Assert
assert "googlemaps" in MIGRATION_BODY and "onboard_ingest" in MIGRATION_BODY, (
"tiles.source CHECK constraint must allow ('googlemaps', 'onboard_ingest')"
)
assert "tile_quality_metadata" in MIGRATION_BODY, (
"tiles must include the onboard-only `tile_quality_metadata` jsonb column"
)
+8
View File
@@ -0,0 +1,8 @@
"""healthcheck smoke — AZ-263 AC-6 (healthcheck importable + runnable)."""
from gps_denied_onboard.healthcheck import check
def test_healthcheck_returns_zero_when_imports_succeed() -> None:
# Act / Assert
assert check() == 0
+40
View File
@@ -0,0 +1,40 @@
"""Structured logging smoke — AZ-263 AC-7."""
import json
import logging
from gps_denied_onboard.logging import get_logger
def test_get_logger_returns_logger_instance() -> None:
# Act
logger = get_logger("test.smoke")
# Assert
assert isinstance(logger, logging.Logger)
def test_log_lines_are_single_json_objects() -> None:
# Arrange
import io
from gps_denied_onboard.logging.structured import _JsonFormatter
stream = io.StringIO()
handler = logging.StreamHandler(stream)
handler.setFormatter(_JsonFormatter())
logger = logging.getLogger("test.json.unit")
logger.handlers = [handler]
logger.setLevel(logging.DEBUG)
logger.propagate = False
# Act
logger.error("hello world", extra={"event": "smoke", "value": 42})
# Assert
line = stream.getvalue().strip().splitlines()[-1]
payload = json.loads(line)
assert payload["level"] == "ERROR"
assert payload["msg"] == "hello world"
assert payload["event"] == "smoke"
assert payload["value"] == 42
+52
View File
@@ -0,0 +1,52 @@
"""Runtime-root env-var fail-fast — AZ-263 AC-8."""
import pytest
from gps_denied_onboard.runtime_root import ConfigurationError, compose_root
def test_compose_root_fails_fast_on_missing_required(monkeypatch: pytest.MonkeyPatch) -> None:
# Arrange
for var in (
"GPS_DENIED_FC_PROFILE",
"GPS_DENIED_TIER",
"DB_URL",
"CAMERA_CALIBRATION_PATH",
"LOG_LEVEL",
"LOG_SINK",
"INFERENCE_BACKEND",
"FDR_PATH",
"TILE_CACHE_PATH",
"MAVLINK_SIGNING_KEY",
):
monkeypatch.delenv(var, raising=False)
# Act / Assert
with pytest.raises(ConfigurationError) as excinfo:
compose_root()
assert "Missing required environment variable" in str(excinfo.value)
def test_compose_root_names_the_first_missing_var(monkeypatch: pytest.MonkeyPatch) -> None:
# Arrange
for var in (
"GPS_DENIED_FC_PROFILE",
"GPS_DENIED_TIER",
"DB_URL",
"CAMERA_CALIBRATION_PATH",
"LOG_LEVEL",
"LOG_SINK",
"INFERENCE_BACKEND",
"FDR_PATH",
"TILE_CACHE_PATH",
"MAVLINK_SIGNING_KEY",
):
monkeypatch.delenv(var, raising=False)
# Act
with pytest.raises(ConfigurationError) as excinfo:
compose_root()
# Assert
msg = str(excinfo.value)
assert "GPS_DENIED_FC_PROFILE" in msg
+22
View File
@@ -0,0 +1,22 @@
"""Cross-component DTO importability — AZ-263 AC-2.
Mirrors the AC's specified `python -c "from gps_denied_onboard._types import ..."`.
"""
def test_types_modules_importable() -> None:
# Assert
from gps_denied_onboard._types import (
calibration,
emitted,
manifests,
matching,
nav,
pose,
tile,
vio,
vpr,
)
for mod in (nav, vio, vpr, matching, pose, tile, calibration, emitted, manifests):
assert mod is not None