Files
gps-denied-onboard/e2e
Oleksandr Bezdieniezhnykh c56d4584e6 [AZ-436] [AZ-437] [AZ-438] [AZ-439] Add NFT-SEC-01..05 security scenarios
Batch 87: 6 NFT-SEC blackbox scenarios + 5 helper evaluators + 75 unit
tests + cumulative review batches 85-87.

* AZ-436 NFT-SEC-01: cache-poisoning safety budget (AC-NEW-9); aggregate
  false_trust_count ≤ N×1e-6; zero-tolerance default. Canonical-only by
  default; E2E_NFT_SEC_01_RELEASE_GATE=1 unlocks full matrix.
* AZ-437 NFT-SEC-02 + NFT-SEC-05: shared egress-observation evaluator
  (AC-NEW-10); SEC-02 = 0 packets to non-e2e-net over 5min replay;
  SEC-05 = DNS-blackhole sidecar healthy + lookup fails + UDP-53 silent.
* AZ-438 NFT-SEC-03: AP-only signing rejection (AC-NEW-11); 3 sub-cases
  (unsigned/wrong-key/replayed) each reject ≤500ms + no position drift.
* AZ-439 NFT-SEC-04: probe (always-run) = no-crash + deterministic
  decode outcome; ASan-fuzz (release-gate) = 0 findings ≥4h; AC-3
  corpus floor informational only per spec.

Verdict per-batch: PASS_WITH_WARNINGS (5 Low). Cumulative review for
batches 85-87 (K=3 window) also PASS_WITH_WARNINGS with 5 cross-batch
findings — recommends hygiene PBIs for write_csv_evidence duplication
(13 helpers) and _resolve_fixture_path duplication (13 scenarios), plus
new tickets for AZ-595 fixture builder + DNS-blackhole sidecar service.

Also adds _docs/LESSONS.md documenting the Jira transition-ID lesson
(always call getTransitionsForJiraIssue first, never memorize numeric
IDs across sessions).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 17:33:22 +03:00
..

Blackbox Test Harness (e2e/)

This directory is the public-boundary test harness for gps-denied-onboard. It is owned by the blackbox_tests cross-cutting entry in _docs/02_document/module-layout.md and implements task AZ-406 (Test Infrastructure Bootstrap) plus its downstream test-task siblings (AZ-407..AZ-446).

The harness runs in two execution tiers (environment.md § Two-tier execution profile):

  • Tier-1 — workstation Docker. cd e2e/docker && docker compose -f docker-compose.test.yml up --build --abort-on-container-exit e2e-runner
  • Tier-2 — Jetson Orin Nano Super hardware loop. ./e2e/jetson/run-tier2.sh --fc-adapter <ardupilot|inav> --vio-strategy <okvis2|klt_ransac>

Both tiers emit the same CSV report format (one row per test) per environment.md § Reporting.

Layout

e2e/
├── docker/        Tier-1 entrypoint (docker-compose.test.yml + Tier-2 bridge override + secrets mount)
├── jetson/        Tier-2 entrypoint (run-tier2.sh + systemd unit + tegrastats/jtop parsers)
├── runner/        e2e-runner image (Dockerfile, conftest, pytest plugins, helpers, requirements)
├── fixtures/      Fixture builders (tile-cache, age-injector, injectors/, mock-suite-sat, secrets, security)
├── tests/         Pytest target — `positive/`, `negative/`, `performance/`, `resilience/`, `security/`, `resource_limit/`
└── _unit_tests/   Out-of-container unit tests for the harness internals (run as part of the project test suite)

Public-Boundary Discipline (hard rule)

The e2e-runner image MUST NOT import any module from the SUT source tree (src/gps_denied_onboard/**). The only legal interaction surfaces are:

  • MAVLink (ArduPilot SITL — UDP 14550)
  • MSP2 (iNav SITL — TCP 5760)
  • HTTP/JSON (mock-suite-sat-service — port 8080)
  • Filesystem read of the FDR archive after a run (fdr-output volume)

This rule is enforced by:

  1. The runner Dockerfile building from a base image that does NOT install the SUT package.
  2. Layout discipline: no import gps_denied_onboard.* in any file under e2e/.
  3. Compose e2e-net.internal: true — no external network egress (RESTRICT-SAT-1, NFT-SEC-02).

See _docs/02_document/tests/environment.md for the full per-service spec.

RUN_ID and report paths

Each invocation must set RUN_ID (defaults to local-${USER}-${EPOCH} in development; CI sets it from the workflow run id). Reports land at:

  • e2e-results/run-${RUN_ID}/report.csv
  • e2e-results/run-${RUN_ID}/evidence/ (per-run .tlog, FDR archives, screenshots, profiler traces, tegrastats CSV, jtop CSV)

The e2e-results/ directory is gitignored.

How to add a new blackbox scenario

  1. Decompose the scenario into a task spec under _docs/02_tasks/todo/.
  2. Implement the test under the appropriate e2e/tests/<category>/ folder.
  3. The conftest's session-scoped (fc_adapter, vio_strategy) parameterization automatically applies — opt out with @pytest.mark.parametrize overrides.
  4. Trace the scenario to the AC/RESTRICT IDs it exercises via the traces_to pytest marker — the CSV reporter emits this verbatim.

How to add a new fixture builder

Fixture builders live under e2e/fixtures/ and may be standalone Python modules (for runtime injectors) or Dockerized helpers (for tile-cache / mock-suite-sat). Each builder must:

  • Be reproducible — given the same input, produce bit-identical output.
  • Document its output volume / path in _docs/02_document/tests/test-data.md.
  • Have a corresponding unit test under e2e/_unit_tests/fixtures/.

Out-of-container unit tests

The harness's internal Python — CSV reporter, helpers, parsers, mock app, conftest skip rules — is unit-tested under e2e/_unit_tests/. These tests do NOT require Docker, SITL, or any external service and run as part of the project's main pytest invocation (testpaths extension in pyproject.toml).