From 79997e39acc9611b7de9ec817fb918d5984eb5db Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Sun, 3 May 2026 12:04:22 +0300 Subject: [PATCH] [AZ-219] Scaffold onboard runtime project Add the initial source, test, infrastructure, CI, configuration, and evidence-path scaffold so dependent implementation tasks have stable package and runtime boundaries. Co-authored-by: Cursor --- .dockerignore | 27 ++++ .env.example | 10 ++ .github/workflows/ci.yml | 43 ++++++ .gitignore | 41 ++++++ README.md | 21 +++ .../AZ-219_initial_structure.md | 0 .../batch_01_cycle1_report.md | 41 ++++++ .../reviews/batch_01_review.md | 29 ++++ _docs/_autodev_state.md | 6 +- config/ci/runtime.env | 6 + config/development/runtime.env | 6 + config/jetson/runtime.env | 6 + config/production/runtime.env.example | 10 ++ data/cache/.gitkeep | 1 + data/expected/.gitkeep | 1 + data/fdr/.gitkeep | 1 + data/input/.gitkeep | 1 + data/test-results/.gitkeep | 1 + deployment/compose/README.md | 4 + deployment/docker/Dockerfile.replay | 19 +++ deployment/docker/Dockerfile.runtime | 18 +++ deployment/jetson/README.md | 4 + deployment/scripts/collect_evidence.sh | 5 + docker-compose.test.yml | 27 ++++ docker-compose.yml | 34 +++++ e2e/replay/README.md | 4 + e2e/replay/run_replay.py | 14 ++ e2e/reports/.gitkeep | 1 + migrations/postgresql/0001_enable_postgis.sql | 1 + migrations/seed/README.md | 4 + pyproject.toml | 32 +++++ src/__init__.py | 1 + src/anchor_verification/__init__.py | 1 + src/anchor_verification/interfaces.py | 10 ++ src/anchor_verification/types.py | 5 + src/basalt_vio_adapter/__init__.py | 1 + src/basalt_vio_adapter/interfaces.py | 13 ++ src/basalt_vio_adapter/types.py | 5 + src/camera_ingest_calibration/__init__.py | 1 + src/camera_ingest_calibration/interfaces.py | 10 ++ src/camera_ingest_calibration/types.py | 5 + src/fdr_observability/__init__.py | 1 + src/fdr_observability/interfaces.py | 13 ++ src/fdr_observability/types.py | 5 + src/mavlink_gcs_integration/__init__.py | 1 + src/mavlink_gcs_integration/interfaces.py | 13 ++ src/mavlink_gcs_integration/types.py | 5 + src/native/basalt_bridge/README.md | 3 + src/native/feature_matching/README.md | 3 + src/native/tensor_rt/README.md | 3 + src/safety_anchor_wrapper/__init__.py | 1 + src/safety_anchor_wrapper/interfaces.py | 13 ++ src/safety_anchor_wrapper/types.py | 5 + src/satellite_service/__init__.py | 1 + src/satellite_service/interfaces.py | 13 ++ src/satellite_service/types.py | 5 + src/shared/__init__.py | 1 + src/shared/config/__init__.py | 1 + src/shared/contracts/__init__.py | 3 + src/shared/errors/__init__.py | 1 + src/shared/geo_geometry/__init__.py | 1 + src/shared/telemetry/__init__.py | 1 + src/shared/time_sync/__init__.py | 1 + src/tile_manager/__init__.py | 1 + src/tile_manager/interfaces.py | 13 ++ src/tile_manager/types.py | 5 + tests/blackbox/cache_freshness/.gitkeep | 1 + tests/blackbox/resource_limits/.gitkeep | 1 + tests/blackbox/run_blackbox.py | 18 +++ tests/blackbox/satellite_anchor/.gitkeep | 1 + .../blackbox/still_image_geolocation/.gitkeep | 1 + .../visual_blackout_spoofing/.gitkeep | 1 + tests/e2e/release_evidence/.gitkeep | 1 + tests/e2e/replay/.gitkeep | 1 + tests/e2e/reports/.gitkeep | 1 + tests/fixtures/expected_results/.gitkeep | 1 + tests/fixtures/project_60_images/.gitkeep | 1 + tests/fixtures/public_dataset_slices/.gitkeep | 1 + tests/fixtures/satellite_cache/.gitkeep | 1 + tests/fixtures/telemetry/.gitkeep | 1 + tests/integration/cache_postgis/.gitkeep | 1 + tests/integration/contracts/.gitkeep | 1 + tests/integration/fdr/.gitkeep | 1 + tests/integration/mavlink/.gitkeep | 1 + tests/sitl/failsafe/.gitkeep | 1 + tests/sitl/plane_gps_input/.gitkeep | 1 + tests/sitl/spoofing_promotion/.gitkeep | 1 + tests/unit/anchor_verification/.gitkeep | 1 + tests/unit/basalt_vio_adapter/.gitkeep | 1 + tests/unit/camera_ingest_calibration/.gitkeep | 1 + tests/unit/fdr_observability/.gitkeep | 1 + tests/unit/mavlink_gcs_integration/.gitkeep | 1 + tests/unit/safety_anchor_wrapper/.gitkeep | 1 + tests/unit/satellite_service/.gitkeep | 1 + tests/unit/shared/.gitkeep | 1 + tests/unit/test_scaffold.py | 126 ++++++++++++++++++ tests/unit/tile_manager/.gitkeep | 1 + 97 files changed, 753 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 README.md rename _docs/02_tasks/{todo => done}/AZ-219_initial_structure.md (100%) create mode 100644 _docs/03_implementation/batch_01_cycle1_report.md create mode 100644 _docs/03_implementation/reviews/batch_01_review.md create mode 100644 config/ci/runtime.env create mode 100644 config/development/runtime.env create mode 100644 config/jetson/runtime.env create mode 100644 config/production/runtime.env.example create mode 100644 data/cache/.gitkeep create mode 100644 data/expected/.gitkeep create mode 100644 data/fdr/.gitkeep create mode 100644 data/input/.gitkeep create mode 100644 data/test-results/.gitkeep create mode 100644 deployment/compose/README.md create mode 100644 deployment/docker/Dockerfile.replay create mode 100644 deployment/docker/Dockerfile.runtime create mode 100644 deployment/jetson/README.md create mode 100644 deployment/scripts/collect_evidence.sh create mode 100644 docker-compose.test.yml create mode 100644 docker-compose.yml create mode 100644 e2e/replay/README.md create mode 100644 e2e/replay/run_replay.py create mode 100644 e2e/reports/.gitkeep create mode 100644 migrations/postgresql/0001_enable_postgis.sql create mode 100644 migrations/seed/README.md create mode 100644 pyproject.toml create mode 100644 src/__init__.py create mode 100644 src/anchor_verification/__init__.py create mode 100644 src/anchor_verification/interfaces.py create mode 100644 src/anchor_verification/types.py create mode 100644 src/basalt_vio_adapter/__init__.py create mode 100644 src/basalt_vio_adapter/interfaces.py create mode 100644 src/basalt_vio_adapter/types.py create mode 100644 src/camera_ingest_calibration/__init__.py create mode 100644 src/camera_ingest_calibration/interfaces.py create mode 100644 src/camera_ingest_calibration/types.py create mode 100644 src/fdr_observability/__init__.py create mode 100644 src/fdr_observability/interfaces.py create mode 100644 src/fdr_observability/types.py create mode 100644 src/mavlink_gcs_integration/__init__.py create mode 100644 src/mavlink_gcs_integration/interfaces.py create mode 100644 src/mavlink_gcs_integration/types.py create mode 100644 src/native/basalt_bridge/README.md create mode 100644 src/native/feature_matching/README.md create mode 100644 src/native/tensor_rt/README.md create mode 100644 src/safety_anchor_wrapper/__init__.py create mode 100644 src/safety_anchor_wrapper/interfaces.py create mode 100644 src/safety_anchor_wrapper/types.py create mode 100644 src/satellite_service/__init__.py create mode 100644 src/satellite_service/interfaces.py create mode 100644 src/satellite_service/types.py create mode 100644 src/shared/__init__.py create mode 100644 src/shared/config/__init__.py create mode 100644 src/shared/contracts/__init__.py create mode 100644 src/shared/errors/__init__.py create mode 100644 src/shared/geo_geometry/__init__.py create mode 100644 src/shared/telemetry/__init__.py create mode 100644 src/shared/time_sync/__init__.py create mode 100644 src/tile_manager/__init__.py create mode 100644 src/tile_manager/interfaces.py create mode 100644 src/tile_manager/types.py create mode 100644 tests/blackbox/cache_freshness/.gitkeep create mode 100644 tests/blackbox/resource_limits/.gitkeep create mode 100644 tests/blackbox/run_blackbox.py create mode 100644 tests/blackbox/satellite_anchor/.gitkeep create mode 100644 tests/blackbox/still_image_geolocation/.gitkeep create mode 100644 tests/blackbox/visual_blackout_spoofing/.gitkeep create mode 100644 tests/e2e/release_evidence/.gitkeep create mode 100644 tests/e2e/replay/.gitkeep create mode 100644 tests/e2e/reports/.gitkeep create mode 100644 tests/fixtures/expected_results/.gitkeep create mode 100644 tests/fixtures/project_60_images/.gitkeep create mode 100644 tests/fixtures/public_dataset_slices/.gitkeep create mode 100644 tests/fixtures/satellite_cache/.gitkeep create mode 100644 tests/fixtures/telemetry/.gitkeep create mode 100644 tests/integration/cache_postgis/.gitkeep create mode 100644 tests/integration/contracts/.gitkeep create mode 100644 tests/integration/fdr/.gitkeep create mode 100644 tests/integration/mavlink/.gitkeep create mode 100644 tests/sitl/failsafe/.gitkeep create mode 100644 tests/sitl/plane_gps_input/.gitkeep create mode 100644 tests/sitl/spoofing_promotion/.gitkeep create mode 100644 tests/unit/anchor_verification/.gitkeep create mode 100644 tests/unit/basalt_vio_adapter/.gitkeep create mode 100644 tests/unit/camera_ingest_calibration/.gitkeep create mode 100644 tests/unit/fdr_observability/.gitkeep create mode 100644 tests/unit/mavlink_gcs_integration/.gitkeep create mode 100644 tests/unit/safety_anchor_wrapper/.gitkeep create mode 100644 tests/unit/satellite_service/.gitkeep create mode 100644 tests/unit/shared/.gitkeep create mode 100644 tests/unit/test_scaffold.py create mode 100644 tests/unit/tile_manager/.gitkeep diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d5d317a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +.git +.github +.cursor +_docs +.venv +__pycache__ +.pytest_cache +.ruff_cache +.mypy_cache +.env +.env.* +*.pem +*.key +*.secret +data/input/* +data/cache/* +data/fdr/* +data/test-results/* +*.tlog +*.ulg +*.bag +*.mcap +*.cbor +*.parquet +*.mp4 +*.mov +*.avi diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..94e50d3 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +GPSD_ENV=development +GPSD_CONFIG_DIR=./config/development +GPSD_CACHE_DIR=./data/cache +GPSD_FDR_DIR=./data/fdr +GPSD_DATABASE_URL=postgresql://gpsd:gpsd@localhost:5432/gpsd +GPSD_MAVLINK_URL=udp:127.0.0.1:14550 +GPSD_CAMERA_SOURCE=./data/input +GPSD_SIGNING_KEY_REF=test-key-ref +GPSD_MAX_FDR_BYTES=104857600 +GPSD_LOG_LEVEL=info diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..74f23f4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + pull_request: + push: + branches: + - dev + +jobs: + python-quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[dev]" + - name: Format check + run: python -m black --check src tests + - name: Lint + run: python -m ruff check src tests + - name: Unit tests + run: python -m pytest tests/unit + + replay-compose-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate compose files + run: | + docker compose -f docker-compose.yml config + docker compose -f docker-compose.test.yml config + - name: Collect artifact placeholders + run: mkdir -p data/test-results e2e/reports + - uses: actions/upload-artifact@v4 + with: + name: replay-evidence-placeholders + path: | + data/test-results + e2e/reports diff --git a/.gitignore b/.gitignore index e43b0f9..ceaba45 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,42 @@ .DS_Store +.venv/ +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.coverage +htmlcov/ +*.egg-info/ + +.env +.env.* +!.env.example +*.pem +*.key +*.secret + +data/input/* +data/cache/* +data/fdr/* +data/test-results/* +data/expected/* +!data/input/.gitkeep +!data/cache/.gitkeep +!data/fdr/.gitkeep +!data/test-results/.gitkeep +!data/expected/.gitkeep + +*.tlog +*.ulg +*.bag +*.mcap +*.cbor +*.parquet +*.mp4 +*.mov +*.avi +*.jpg +*.jpeg +*.png +!_docs/00_problem/input_data/** diff --git a/README.md b/README.md new file mode 100644 index 0000000..63fcc46 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# GPS-Denied Onboard Runtime + +Scaffold for the Jetson-hosted GPS-denied localization runtime, replay harness, and +deployment evidence paths. + +The project uses a Python `src/` layout for orchestration code, with native bridge +directories reserved for BASALT, feature matching, and TensorRT integrations. +Generated mission data, FDR payloads, cache payloads, and raw frame dumps are kept +out of git unless they are explicitly curated test fixtures. + +## Local Development + +```bash +python3 -m venv .venv +source .venv/bin/activate +python -m pip install -e ".[dev]" +python -m pytest +``` + +Local replay infrastructure is described in `docker-compose.yml`; CI and black-box +test infrastructure are described in `docker-compose.test.yml`. diff --git a/_docs/02_tasks/todo/AZ-219_initial_structure.md b/_docs/02_tasks/done/AZ-219_initial_structure.md similarity index 100% rename from _docs/02_tasks/todo/AZ-219_initial_structure.md rename to _docs/02_tasks/done/AZ-219_initial_structure.md diff --git a/_docs/03_implementation/batch_01_cycle1_report.md b/_docs/03_implementation/batch_01_cycle1_report.md new file mode 100644 index 0000000..2fb8249 --- /dev/null +++ b/_docs/03_implementation/batch_01_cycle1_report.md @@ -0,0 +1,41 @@ +# Batch Report + +**Batch**: 1 +**Tasks**: AZ-219_initial_structure +**Date**: 2026-05-03 + +## Task Results + +| Task | Status | Files Modified | Tests | AC Coverage | Issues | +|------|--------|----------------|-------|-------------|--------| +| AZ-219_initial_structure | Done | 98 files | Pass | 7/7 ACs covered | None | + +## AC Test Coverage: All covered + +| AC Ref | Coverage | +|--------|----------| +| AC-1 | `test_scaffold_paths_cover_runtime_test_and_evidence_layout` verifies source, tests, migrations, deployment, configuration, data, CI, and compose scaffold paths. | +| AC-2 | `test_runtime_component_public_modules_are_importable` and `test_shared_contract_locations_are_importable` verify public component and shared contract namespaces. | +| AC-3 | `test_scaffold_paths_cover_runtime_test_and_evidence_layout` verifies compose, env template, and migration paths; compose config validation passed. | +| AC-4 | `test_scaffold_paths_cover_runtime_test_and_evidence_layout` verifies `.github/workflows/ci.yml`; the workflow defines format, lint, unit test, compose config, and artifact placeholder jobs. | +| AC-5 | `test_scaffold_paths_cover_runtime_test_and_evidence_layout` verifies unit, integration, black-box, fixture, SITL, and e2e runner paths. | +| AC-6 | `test_scaffold_paths_cover_runtime_test_and_evidence_layout` verifies deployment scripts and evidence report paths. | +| AC-7 | `test_ignore_rules_exclude_runtime_payloads_and_secrets` verifies secrets, raw frames, cache/FDR payloads, and test result artifacts are ignored. | + +## Code Review Verdict: PASS + +Review report: `_docs/03_implementation/reviews/batch_01_review.md` + +## Auto-Fix Attempts: 0 + +## Stuck Agents: None + +## Verification + +- `.venv/bin/python -m black --check src tests e2e/replay` passed. +- `.venv/bin/python -m ruff check src tests e2e/replay` passed. +- `.venv/bin/python -m pytest` passed: 5 tests. +- `docker compose -f docker-compose.yml config` passed. +- `docker compose -f docker-compose.test.yml config` passed. + +## Next Batch: AZ-220_shared_runtime_contracts diff --git a/_docs/03_implementation/reviews/batch_01_review.md b/_docs/03_implementation/reviews/batch_01_review.md new file mode 100644 index 0000000..757ecb4 --- /dev/null +++ b/_docs/03_implementation/reviews/batch_01_review.md @@ -0,0 +1,29 @@ +# Code Review Report + +**Batch**: AZ-219_initial_structure +**Date**: 2026-05-03 +**Verdict**: PASS + +## Findings + +| # | Severity | Category | File:Line | Title | +|---|----------|----------|-----------|-------| +| - | - | - | - | No findings | + +## Review Notes + +- AC-1 is satisfied by the `src/`, `migrations/`, `tests/`, `e2e/`, `deployment/`, `config/`, and `data/` scaffold plus tracked placeholders. +- AC-2 is satisfied by importable component and shared package namespaces under `src/`. +- AC-3 is satisfied by `docker-compose.yml`, `docker-compose.test.yml`, `.env.example`, and the initial PostGIS migration. +- AC-4 is satisfied by `.github/workflows/ci.yml` with format, lint, unit-test, compose-config, and artifact placeholder stages. +- AC-5 is satisfied by pytest unit scaffold coverage and black-box/SITL/e2e fixture entry-point directories. +- AC-6 is satisfied by deployment Dockerfiles, Jetson/deployment placeholders, `e2e/reports/`, and `deployment/scripts/collect_evidence.sh`. +- AC-7 is satisfied by `.gitignore`, `.dockerignore`, and non-secret environment templates excluding generated runtime payloads and credentials. + +## Verification + +- `.venv/bin/python -m black --check src tests e2e/replay` passed. +- `.venv/bin/python -m ruff check src tests e2e/replay` passed. +- `.venv/bin/python -m pytest` passed: 5 tests. +- `docker compose -f docker-compose.yml config` passed. +- `docker compose -f docker-compose.test.yml config` passed. diff --git a/_docs/_autodev_state.md b/_docs/_autodev_state.md index 145d500..0cf09c9 100644 --- a/_docs/_autodev_state.md +++ b/_docs/_autodev_state.md @@ -7,8 +7,8 @@ name: Implement status: in_progress tracker: jira sub_step: - phase: 0 - name: awaiting-invocation - detail: "" + phase: 1 + name: batch-loop + detail: "batch 1: AZ-219_initial_structure" retry_count: 0 cycle: 1 diff --git a/config/ci/runtime.env b/config/ci/runtime.env new file mode 100644 index 0000000..bb54efc --- /dev/null +++ b/config/ci/runtime.env @@ -0,0 +1,6 @@ +GPSD_ENV=ci +GPSD_LOG_LEVEL=info +GPSD_CONFIG_DIR=./config/ci +GPSD_CACHE_DIR=./data/cache +GPSD_FDR_DIR=./data/fdr +GPSD_CAMERA_SOURCE=./tests/fixtures diff --git a/config/development/runtime.env b/config/development/runtime.env new file mode 100644 index 0000000..fa6e3e0 --- /dev/null +++ b/config/development/runtime.env @@ -0,0 +1,6 @@ +GPSD_ENV=development +GPSD_LOG_LEVEL=debug +GPSD_CONFIG_DIR=./config/development +GPSD_CACHE_DIR=./data/cache +GPSD_FDR_DIR=./data/fdr +GPSD_CAMERA_SOURCE=./data/input diff --git a/config/jetson/runtime.env b/config/jetson/runtime.env new file mode 100644 index 0000000..daf8330 --- /dev/null +++ b/config/jetson/runtime.env @@ -0,0 +1,6 @@ +GPSD_ENV=jetson +GPSD_LOG_LEVEL=info +GPSD_CONFIG_DIR=/etc/gps-denied-onboard +GPSD_CACHE_DIR=/var/lib/gps-denied/cache +GPSD_FDR_DIR=/var/lib/gps-denied/fdr +GPSD_CAMERA_SOURCE=hardware diff --git a/config/production/runtime.env.example b/config/production/runtime.env.example new file mode 100644 index 0000000..480c6aa --- /dev/null +++ b/config/production/runtime.env.example @@ -0,0 +1,10 @@ +GPSD_ENV=production +GPSD_LOG_LEVEL=info +GPSD_CONFIG_DIR=/etc/gps-denied-onboard +GPSD_CACHE_DIR=/var/lib/gps-denied/cache +GPSD_FDR_DIR=/var/lib/gps-denied/fdr +GPSD_DATABASE_URL=postgresql://user:password@localhost:5432/gpsd +GPSD_MAVLINK_URL=serial:/dev/ttyTHS1:921600 +GPSD_CAMERA_SOURCE=hardware +GPSD_SIGNING_KEY_REF=replace-with-secret-manager-reference +GPSD_MAX_FDR_BYTES=68719476736 diff --git a/data/cache/.gitkeep b/data/cache/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/cache/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/expected/.gitkeep b/data/expected/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/expected/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/fdr/.gitkeep b/data/fdr/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/fdr/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/input/.gitkeep b/data/input/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/input/.gitkeep @@ -0,0 +1 @@ + diff --git a/data/test-results/.gitkeep b/data/test-results/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/data/test-results/.gitkeep @@ -0,0 +1 @@ + diff --git a/deployment/compose/README.md b/deployment/compose/README.md new file mode 100644 index 0000000..b0a34b3 --- /dev/null +++ b/deployment/compose/README.md @@ -0,0 +1,4 @@ +# Compose Configuration + +Runtime compose files live at the repository root so local and CI commands can +use Docker defaults without extra path arguments. diff --git a/deployment/docker/Dockerfile.replay b/deployment/docker/Dockerfile.replay new file mode 100644 index 0000000..ae199a5 --- /dev/null +++ b/deployment/docker/Dockerfile.replay @@ -0,0 +1,19 @@ +FROM python:3.12-slim-bookworm + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN groupadd --system gpsd && useradd --system --gid gpsd --home-dir /app gpsd + +COPY pyproject.toml README.md ./ +COPY src ./src +COPY tests ./tests + +RUN python -m pip install --no-cache-dir --upgrade pip \ + && python -m pip install --no-cache-dir ".[dev]" + +USER gpsd + +CMD ["python", "-m", "pytest"] diff --git a/deployment/docker/Dockerfile.runtime b/deployment/docker/Dockerfile.runtime new file mode 100644 index 0000000..83a8ed5 --- /dev/null +++ b/deployment/docker/Dockerfile.runtime @@ -0,0 +1,18 @@ +FROM python:3.12-slim-bookworm + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN groupadd --system gpsd && useradd --system --gid gpsd --home-dir /app gpsd + +COPY pyproject.toml README.md ./ +COPY src ./src + +RUN python -m pip install --no-cache-dir --upgrade pip \ + && python -m pip install --no-cache-dir . + +USER gpsd + +CMD ["python", "-c", "import shared.contracts; print('gps-denied runtime scaffold ready')"] diff --git a/deployment/jetson/README.md b/deployment/jetson/README.md new file mode 100644 index 0000000..2746324 --- /dev/null +++ b/deployment/jetson/README.md @@ -0,0 +1,4 @@ +# Jetson Deployment Notes + +Reserved for Jetson provisioning, thermal/resource evidence, and hardware-runner +qualification procedures. diff --git a/deployment/scripts/collect_evidence.sh b/deployment/scripts/collect_evidence.sh new file mode 100644 index 0000000..ac8a63e --- /dev/null +++ b/deployment/scripts/collect_evidence.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p e2e/reports data/test-results +printf 'Evidence collection placeholder. Wire runtime reports in deploy phase.\n' diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..89858e3 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,27 @@ +services: + postgis: + image: postgis/postgis:16-3.4 + environment: + POSTGRES_DB: gpsd_test + POSTGRES_USER: gpsd + POSTGRES_PASSWORD: gpsd + volumes: + - ./migrations/postgresql:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U gpsd -d gpsd_test"] + interval: 10s + timeout: 5s + retries: 5 + + replay-tests: + build: + context: . + dockerfile: deployment/docker/Dockerfile.replay + env_file: + - config/ci/runtime.env + depends_on: + postgis: + condition: service_healthy + volumes: + - ./tests/fixtures:/app/tests/fixtures:ro + - ./data/test-results:/app/data/test-results diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ef34411 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +services: + postgis: + image: postgis/postgis:16-3.4 + environment: + POSTGRES_DB: gpsd + POSTGRES_USER: gpsd + POSTGRES_PASSWORD: gpsd + ports: + - "5432:5432" + volumes: + - postgis-data:/var/lib/postgresql/data + - ./migrations/postgresql:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U gpsd -d gpsd"] + interval: 10s + timeout: 5s + retries: 5 + + runtime: + build: + context: . + dockerfile: deployment/docker/Dockerfile.runtime + env_file: + - .env.example + depends_on: + postgis: + condition: service_healthy + volumes: + - ./data/cache:/app/data/cache + - ./data/fdr:/app/data/fdr + - ./data/input:/app/data/input:ro + +volumes: + postgis-data: diff --git a/e2e/replay/README.md b/e2e/replay/README.md new file mode 100644 index 0000000..8846728 --- /dev/null +++ b/e2e/replay/README.md @@ -0,0 +1,4 @@ +# Replay Harness + +Reserved for suite-level replay entry points that drive the runtime only through +public files, MAVLink/cache/FDR interfaces, and published reports. diff --git a/e2e/replay/run_replay.py b/e2e/replay/run_replay.py new file mode 100644 index 0000000..7e9895c --- /dev/null +++ b/e2e/replay/run_replay.py @@ -0,0 +1,14 @@ +"""Replay runner entry point.""" + +from pathlib import Path + + +def main() -> int: + report_path = Path("e2e/reports/replay_smoke.txt") + report_path.parent.mkdir(parents=True, exist_ok=True) + report_path.write_text("replay scaffold ready\n", encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/e2e/reports/.gitkeep b/e2e/reports/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/e2e/reports/.gitkeep @@ -0,0 +1 @@ + diff --git a/migrations/postgresql/0001_enable_postgis.sql b/migrations/postgresql/0001_enable_postgis.sql new file mode 100644 index 0000000..576e542 --- /dev/null +++ b/migrations/postgresql/0001_enable_postgis.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS postgis; diff --git a/migrations/seed/README.md b/migrations/seed/README.md new file mode 100644 index 0000000..4c7259f --- /dev/null +++ b/migrations/seed/README.md @@ -0,0 +1,4 @@ +# Seed Data + +Place deterministic development and CI seed data here. Do not add production +mission payloads, signing material, or raw frame dumps. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..eb87e0d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "gps-denied-onboard" +version = "0.1.0" +description = "Jetson-hosted GPS-denied localization runtime scaffold." +requires-python = ">=3.10" +dependencies = [] + +[project.optional-dependencies] +dev = [ + "black>=24.0", + "pytest>=8.0", + "ruff>=0.5", +] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] + +[tool.ruff] +line-length = 100 +src = ["src", "tests"] + +[tool.black] +line-length = 100 +target-version = ["py310"] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..858fdca --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +"""Source-root package marker for editable installs.""" diff --git a/src/anchor_verification/__init__.py b/src/anchor_verification/__init__.py new file mode 100644 index 0000000..5205f32 --- /dev/null +++ b/src/anchor_verification/__init__.py @@ -0,0 +1 @@ +"""Anchor verification component.""" diff --git a/src/anchor_verification/interfaces.py b/src/anchor_verification/interfaces.py new file mode 100644 index 0000000..89032c7 --- /dev/null +++ b/src/anchor_verification/interfaces.py @@ -0,0 +1,10 @@ +"""Public anchor verification interfaces.""" + +from typing import Any, Protocol + + +class AnchorVerifier(Protocol): + """Verifies retrieved candidates against camera observations.""" + + def verify(self, frame: Any, candidate: Any) -> Any: + """Return an anchor decision for one candidate.""" diff --git a/src/anchor_verification/types.py b/src/anchor_verification/types.py new file mode 100644 index 0000000..6d2e069 --- /dev/null +++ b/src/anchor_verification/types.py @@ -0,0 +1,5 @@ +"""Public anchor verification type aliases.""" + +from typing import Any + +AnchorDecisionLike = Any diff --git a/src/basalt_vio_adapter/__init__.py b/src/basalt_vio_adapter/__init__.py new file mode 100644 index 0000000..2708e7b --- /dev/null +++ b/src/basalt_vio_adapter/__init__.py @@ -0,0 +1 @@ +"""BASALT VIO adapter component.""" diff --git a/src/basalt_vio_adapter/interfaces.py b/src/basalt_vio_adapter/interfaces.py new file mode 100644 index 0000000..071b871 --- /dev/null +++ b/src/basalt_vio_adapter/interfaces.py @@ -0,0 +1,13 @@ +"""Public BASALT VIO adapter interfaces.""" + +from typing import Any, Protocol + + +class VioAdapter(Protocol): + """Processes frame and telemetry inputs into relative VIO state.""" + + def initialize(self) -> None: + """Initialize adapter resources.""" + + def process(self, frame: Any, telemetry: Any) -> Any: + """Process one synchronized frame/telemetry pair.""" diff --git a/src/basalt_vio_adapter/types.py b/src/basalt_vio_adapter/types.py new file mode 100644 index 0000000..2bad1eb --- /dev/null +++ b/src/basalt_vio_adapter/types.py @@ -0,0 +1,5 @@ +"""Public BASALT VIO type aliases.""" + +from typing import Any + +VioStatePacketLike = Any diff --git a/src/camera_ingest_calibration/__init__.py b/src/camera_ingest_calibration/__init__.py new file mode 100644 index 0000000..a753ca0 --- /dev/null +++ b/src/camera_ingest_calibration/__init__.py @@ -0,0 +1 @@ +"""Camera ingest and calibration component.""" diff --git a/src/camera_ingest_calibration/interfaces.py b/src/camera_ingest_calibration/interfaces.py new file mode 100644 index 0000000..85fd40f --- /dev/null +++ b/src/camera_ingest_calibration/interfaces.py @@ -0,0 +1,10 @@ +"""Public camera ingest interfaces.""" + +from typing import Any, Protocol + + +class FrameProvider(Protocol): + """Source of navigation frames for downstream localization components.""" + + def next_frame(self) -> Any: + """Return the next frame packet.""" diff --git a/src/camera_ingest_calibration/types.py b/src/camera_ingest_calibration/types.py new file mode 100644 index 0000000..3ff1b93 --- /dev/null +++ b/src/camera_ingest_calibration/types.py @@ -0,0 +1,5 @@ +"""Public camera ingest type aliases.""" + +from typing import Any + +FramePacketLike = Any diff --git a/src/fdr_observability/__init__.py b/src/fdr_observability/__init__.py new file mode 100644 index 0000000..3ce1fa4 --- /dev/null +++ b/src/fdr_observability/__init__.py @@ -0,0 +1 @@ +"""Flight data recorder and observability component.""" diff --git a/src/fdr_observability/interfaces.py b/src/fdr_observability/interfaces.py new file mode 100644 index 0000000..920749c --- /dev/null +++ b/src/fdr_observability/interfaces.py @@ -0,0 +1,13 @@ +"""Public flight recorder interfaces.""" + +from typing import Any, Protocol + + +class FlightRecorder(Protocol): + """Append-only event recorder for runtime evidence.""" + + def append_event(self, event: Any) -> None: + """Persist one FDR event.""" + + def export(self) -> Any: + """Export recorded evidence for post-flight analysis.""" diff --git a/src/fdr_observability/types.py b/src/fdr_observability/types.py new file mode 100644 index 0000000..aeae7b2 --- /dev/null +++ b/src/fdr_observability/types.py @@ -0,0 +1,5 @@ +"""Public FDR type aliases.""" + +from typing import Any + +FdrEventLike = Any diff --git a/src/mavlink_gcs_integration/__init__.py b/src/mavlink_gcs_integration/__init__.py new file mode 100644 index 0000000..b829296 --- /dev/null +++ b/src/mavlink_gcs_integration/__init__.py @@ -0,0 +1 @@ +"""MAVLink and GCS integration component.""" diff --git a/src/mavlink_gcs_integration/interfaces.py b/src/mavlink_gcs_integration/interfaces.py new file mode 100644 index 0000000..8c6b897 --- /dev/null +++ b/src/mavlink_gcs_integration/interfaces.py @@ -0,0 +1,13 @@ +"""Public MAVLink gateway interfaces.""" + +from typing import Any, Protocol + + +class MavlinkGateway(Protocol): + """Bridges FC telemetry inputs and localization GPS_INPUT outputs.""" + + def subscribe_telemetry(self) -> Any: + """Subscribe to flight-controller telemetry.""" + + def emit_gps_input(self, estimate: Any) -> None: + """Emit one localization estimate to the flight controller.""" diff --git a/src/mavlink_gcs_integration/types.py b/src/mavlink_gcs_integration/types.py new file mode 100644 index 0000000..f9ca9bd --- /dev/null +++ b/src/mavlink_gcs_integration/types.py @@ -0,0 +1,5 @@ +"""Public MAVLink/GCS type aliases.""" + +from typing import Any + +TelemetrySampleLike = Any diff --git a/src/native/basalt_bridge/README.md b/src/native/basalt_bridge/README.md new file mode 100644 index 0000000..a07a849 --- /dev/null +++ b/src/native/basalt_bridge/README.md @@ -0,0 +1,3 @@ +# BASALT Bridge + +Reserved for native BASALT integration code wrapped by `basalt_vio_adapter`. diff --git a/src/native/feature_matching/README.md b/src/native/feature_matching/README.md new file mode 100644 index 0000000..dea6530 --- /dev/null +++ b/src/native/feature_matching/README.md @@ -0,0 +1,3 @@ +# Feature Matching Bridge + +Reserved for native feature extraction, matching, and RANSAC acceleration code. diff --git a/src/native/tensor_rt/README.md b/src/native/tensor_rt/README.md new file mode 100644 index 0000000..13d0ebc --- /dev/null +++ b/src/native/tensor_rt/README.md @@ -0,0 +1,3 @@ +# TensorRT Bridge + +Reserved for Jetson/TensorRT descriptor inference integrations. diff --git a/src/safety_anchor_wrapper/__init__.py b/src/safety_anchor_wrapper/__init__.py new file mode 100644 index 0000000..38b17f1 --- /dev/null +++ b/src/safety_anchor_wrapper/__init__.py @@ -0,0 +1 @@ +"""Safety and anchor wrapper component.""" diff --git a/src/safety_anchor_wrapper/interfaces.py b/src/safety_anchor_wrapper/interfaces.py new file mode 100644 index 0000000..8756467 --- /dev/null +++ b/src/safety_anchor_wrapper/interfaces.py @@ -0,0 +1,13 @@ +"""Public localization state-machine interfaces.""" + +from typing import Any, Protocol + + +class LocalizationStateMachine(Protocol): + """Coordinates VIO propagation and anchor promotion decisions.""" + + def update_vio(self, vio_state: Any) -> Any: + """Update the state machine with a VIO state packet.""" + + def consider_anchor(self, anchor_decision: Any) -> Any: + """Evaluate a verified anchor decision.""" diff --git a/src/safety_anchor_wrapper/types.py b/src/safety_anchor_wrapper/types.py new file mode 100644 index 0000000..b28e98c --- /dev/null +++ b/src/safety_anchor_wrapper/types.py @@ -0,0 +1,5 @@ +"""Public safety wrapper type aliases.""" + +from typing import Any + +PositionEstimateLike = Any diff --git a/src/satellite_service/__init__.py b/src/satellite_service/__init__.py new file mode 100644 index 0000000..7f52eb5 --- /dev/null +++ b/src/satellite_service/__init__.py @@ -0,0 +1 @@ +"""Offline satellite retrieval and synchronization component.""" diff --git a/src/satellite_service/interfaces.py b/src/satellite_service/interfaces.py new file mode 100644 index 0000000..b14373c --- /dev/null +++ b/src/satellite_service/interfaces.py @@ -0,0 +1,13 @@ +"""Public satellite service interfaces.""" + +from typing import Any, Protocol + + +class SatelliteService(Protocol): + """Retrieves offline VPR candidates from mission cache data.""" + + def load_index(self) -> None: + """Load the local descriptor index.""" + + def retrieve(self, frame: Any) -> list[Any]: + """Return candidate anchor records for one frame.""" diff --git a/src/satellite_service/types.py b/src/satellite_service/types.py new file mode 100644 index 0000000..93bea80 --- /dev/null +++ b/src/satellite_service/types.py @@ -0,0 +1,5 @@ +"""Public satellite service type aliases.""" + +from typing import Any + +VprCandidateLike = Any diff --git a/src/shared/__init__.py b/src/shared/__init__.py new file mode 100644 index 0000000..5b1a1e1 --- /dev/null +++ b/src/shared/__init__.py @@ -0,0 +1 @@ +"""Shared runtime foundation packages.""" diff --git a/src/shared/config/__init__.py b/src/shared/config/__init__.py new file mode 100644 index 0000000..9248259 --- /dev/null +++ b/src/shared/config/__init__.py @@ -0,0 +1 @@ +"""Runtime configuration helper namespace.""" diff --git a/src/shared/contracts/__init__.py b/src/shared/contracts/__init__.py new file mode 100644 index 0000000..f1739ef --- /dev/null +++ b/src/shared/contracts/__init__.py @@ -0,0 +1,3 @@ +"""Shared DTO and interface contract namespace.""" + +CONTRACT_VERSION = "0.1.0" diff --git a/src/shared/errors/__init__.py b/src/shared/errors/__init__.py new file mode 100644 index 0000000..ca38b48 --- /dev/null +++ b/src/shared/errors/__init__.py @@ -0,0 +1 @@ +"""Shared error envelope namespace.""" diff --git a/src/shared/geo_geometry/__init__.py b/src/shared/geo_geometry/__init__.py new file mode 100644 index 0000000..6f568b0 --- /dev/null +++ b/src/shared/geo_geometry/__init__.py @@ -0,0 +1 @@ +"""Geospatial geometry helper namespace.""" diff --git a/src/shared/telemetry/__init__.py b/src/shared/telemetry/__init__.py new file mode 100644 index 0000000..9d28594 --- /dev/null +++ b/src/shared/telemetry/__init__.py @@ -0,0 +1 @@ +"""Structured telemetry and health metadata namespace.""" diff --git a/src/shared/time_sync/__init__.py b/src/shared/time_sync/__init__.py new file mode 100644 index 0000000..16f1a7e --- /dev/null +++ b/src/shared/time_sync/__init__.py @@ -0,0 +1 @@ +"""Clock-domain and timestamp helper namespace.""" diff --git a/src/tile_manager/__init__.py b/src/tile_manager/__init__.py new file mode 100644 index 0000000..fafc336 --- /dev/null +++ b/src/tile_manager/__init__.py @@ -0,0 +1 @@ +"""Tile cache and generated tile lifecycle component.""" diff --git a/src/tile_manager/interfaces.py b/src/tile_manager/interfaces.py new file mode 100644 index 0000000..cd0e238 --- /dev/null +++ b/src/tile_manager/interfaces.py @@ -0,0 +1,13 @@ +"""Public tile manager interfaces.""" + +from typing import Any, Protocol + + +class TileManager(Protocol): + """Validates and serves local cache tile records.""" + + def validate_cache(self) -> None: + """Validate local cache metadata and signatures.""" + + def get_tile_window(self, footprint: Any) -> list[Any]: + """Return tiles intersecting a requested footprint.""" diff --git a/src/tile_manager/types.py b/src/tile_manager/types.py new file mode 100644 index 0000000..ff74274 --- /dev/null +++ b/src/tile_manager/types.py @@ -0,0 +1,5 @@ +"""Public tile manager type aliases.""" + +from typing import Any + +CacheTileRecordLike = Any diff --git a/tests/blackbox/cache_freshness/.gitkeep b/tests/blackbox/cache_freshness/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/blackbox/cache_freshness/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/blackbox/resource_limits/.gitkeep b/tests/blackbox/resource_limits/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/blackbox/resource_limits/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/blackbox/run_blackbox.py b/tests/blackbox/run_blackbox.py new file mode 100644 index 0000000..686cf12 --- /dev/null +++ b/tests/blackbox/run_blackbox.py @@ -0,0 +1,18 @@ +"""Black-box runner entry point. + +Future scenarios should call only public runtime inputs and outputs: replay frames, +telemetry, offline cache, MAVLink output, status events, and FDR artifacts. +""" + +from pathlib import Path + + +def main() -> int: + reports_dir = Path("data/test-results") + reports_dir.mkdir(parents=True, exist_ok=True) + (reports_dir / "blackbox_smoke.txt").write_text("blackbox scaffold ready\n", encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/blackbox/satellite_anchor/.gitkeep b/tests/blackbox/satellite_anchor/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/blackbox/satellite_anchor/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/blackbox/still_image_geolocation/.gitkeep b/tests/blackbox/still_image_geolocation/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/blackbox/still_image_geolocation/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/blackbox/visual_blackout_spoofing/.gitkeep b/tests/blackbox/visual_blackout_spoofing/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/blackbox/visual_blackout_spoofing/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/e2e/release_evidence/.gitkeep b/tests/e2e/release_evidence/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/e2e/release_evidence/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/e2e/replay/.gitkeep b/tests/e2e/replay/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/e2e/replay/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/e2e/reports/.gitkeep b/tests/e2e/reports/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/e2e/reports/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/fixtures/expected_results/.gitkeep b/tests/fixtures/expected_results/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/fixtures/expected_results/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/fixtures/project_60_images/.gitkeep b/tests/fixtures/project_60_images/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/fixtures/project_60_images/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/fixtures/public_dataset_slices/.gitkeep b/tests/fixtures/public_dataset_slices/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/fixtures/public_dataset_slices/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/fixtures/satellite_cache/.gitkeep b/tests/fixtures/satellite_cache/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/fixtures/satellite_cache/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/fixtures/telemetry/.gitkeep b/tests/fixtures/telemetry/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/fixtures/telemetry/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/integration/cache_postgis/.gitkeep b/tests/integration/cache_postgis/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/integration/cache_postgis/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/integration/contracts/.gitkeep b/tests/integration/contracts/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/integration/contracts/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/integration/fdr/.gitkeep b/tests/integration/fdr/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/integration/fdr/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/integration/mavlink/.gitkeep b/tests/integration/mavlink/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/integration/mavlink/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/sitl/failsafe/.gitkeep b/tests/sitl/failsafe/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/sitl/failsafe/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/sitl/plane_gps_input/.gitkeep b/tests/sitl/plane_gps_input/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/sitl/plane_gps_input/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/sitl/spoofing_promotion/.gitkeep b/tests/sitl/spoofing_promotion/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/sitl/spoofing_promotion/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/anchor_verification/.gitkeep b/tests/unit/anchor_verification/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/anchor_verification/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/basalt_vio_adapter/.gitkeep b/tests/unit/basalt_vio_adapter/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/basalt_vio_adapter/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/camera_ingest_calibration/.gitkeep b/tests/unit/camera_ingest_calibration/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/camera_ingest_calibration/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/fdr_observability/.gitkeep b/tests/unit/fdr_observability/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/fdr_observability/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/mavlink_gcs_integration/.gitkeep b/tests/unit/mavlink_gcs_integration/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/mavlink_gcs_integration/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/safety_anchor_wrapper/.gitkeep b/tests/unit/safety_anchor_wrapper/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/safety_anchor_wrapper/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/satellite_service/.gitkeep b/tests/unit/satellite_service/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/satellite_service/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/shared/.gitkeep b/tests/unit/shared/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/shared/.gitkeep @@ -0,0 +1 @@ + diff --git a/tests/unit/test_scaffold.py b/tests/unit/test_scaffold.py new file mode 100644 index 0000000..f819f08 --- /dev/null +++ b/tests/unit/test_scaffold.py @@ -0,0 +1,126 @@ +from importlib import import_module +from pathlib import Path + +COMPONENT_PACKAGES = [ + "camera_ingest_calibration", + "basalt_vio_adapter", + "safety_anchor_wrapper", + "satellite_service", + "anchor_verification", + "tile_manager", + "mavlink_gcs_integration", + "fdr_observability", +] + +SHARED_PACKAGES = [ + "shared.contracts", + "shared.geo_geometry", + "shared.time_sync", + "shared.config", + "shared.errors", + "shared.telemetry", +] + +REQUIRED_PATHS = [ + "src", + "migrations/postgresql/0001_enable_postgis.sql", + "migrations/seed/README.md", + "tests/unit/test_scaffold.py", + "tests/unit/shared/.gitkeep", + "tests/unit/camera_ingest_calibration/.gitkeep", + "tests/unit/basalt_vio_adapter/.gitkeep", + "tests/unit/safety_anchor_wrapper/.gitkeep", + "tests/unit/satellite_service/.gitkeep", + "tests/unit/anchor_verification/.gitkeep", + "tests/unit/tile_manager/.gitkeep", + "tests/unit/mavlink_gcs_integration/.gitkeep", + "tests/unit/fdr_observability/.gitkeep", + "tests/integration/contracts/.gitkeep", + "tests/blackbox/still_image_geolocation/.gitkeep", + "tests/fixtures/project_60_images/.gitkeep", + "tests/sitl/plane_gps_input/.gitkeep", + "tests/e2e/replay/.gitkeep", + "e2e/replay/run_replay.py", + "e2e/reports/.gitkeep", + "deployment/docker/Dockerfile.runtime", + "deployment/docker/Dockerfile.replay", + "deployment/scripts/collect_evidence.sh", + "config/development/runtime.env", + "config/ci/runtime.env", + "config/jetson/runtime.env", + "config/production/runtime.env.example", + "docker-compose.yml", + "docker-compose.test.yml", + ".github/workflows/ci.yml", + ".env.example", + ".dockerignore", +] + + +def test_runtime_component_public_modules_are_importable() -> None: + # Act + imported_modules = [ + import_module(module_name) + for package_name in COMPONENT_PACKAGES + for module_name in (package_name, f"{package_name}.interfaces", f"{package_name}.types") + ] + + # Assert + assert len(imported_modules) == len(COMPONENT_PACKAGES) * 3 + + +def test_shared_contract_locations_are_importable() -> None: + # Act + imported_modules = [import_module(package_name) for package_name in SHARED_PACKAGES] + + # Assert + assert len(imported_modules) == len(SHARED_PACKAGES) + + +def test_generated_runtime_data_paths_are_gitkeep_only() -> None: + # Arrange + data_dirs = ["input", "expected", "cache", "fdr", "test-results"] + + # Act + missing = [ + directory for directory in data_dirs if not Path("data", directory, ".gitkeep").is_file() + ] + + # Assert + assert missing == [] + + +def test_scaffold_paths_cover_runtime_test_and_evidence_layout() -> None: + # Act + missing = [path for path in REQUIRED_PATHS if not Path(path).exists()] + + # Assert + assert missing == [] + + +def test_ignore_rules_exclude_runtime_payloads_and_secrets() -> None: + # Arrange + required_patterns = [ + ".env", + "*.pem", + "*.key", + "data/input/*", + "data/cache/*", + "data/fdr/*", + "data/test-results/*", + "*.tlog", + "*.cbor", + "*.mp4", + ] + + # Act + gitignore = Path(".gitignore").read_text(encoding="utf-8") + dockerignore = Path(".dockerignore").read_text(encoding="utf-8") + missing_from_gitignore = [pattern for pattern in required_patterns if pattern not in gitignore] + missing_from_dockerignore = [ + pattern for pattern in required_patterns if pattern not in dockerignore + ] + + # Assert + assert missing_from_gitignore == [] + assert missing_from_dockerignore == [] diff --git a/tests/unit/tile_manager/.gitkeep b/tests/unit/tile_manager/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/unit/tile_manager/.gitkeep @@ -0,0 +1 @@ +