[AZ-406] Add blackbox_tests cross-cutting entry to module-layout.md

The 41 blackbox/e2e test tasks (AZ-406..AZ-446 under epic AZ-262) all
declare Component=Blackbox Tests, but module-layout.md had no matching
Per-Component Mapping entry. The implement skill's Step 4 (File
Ownership) requires every batch's component to be resolvable in
module-layout.md.

Add a `blackbox_tests` entry in the Shared / Cross-Cutting section
that owns the top-level `e2e/` directory (separate from `tests/`),
documents the public-boundary discipline (no SUT imports), and
clarifies that boundary-driven performance/resilience/security
scenarios live under `e2e/tests/<category>/` rather than under
`tests/perf|security|resilience/`.

Also update Layout Rule #7 to reflect the harness split and the
state file's sub_step to parse-and-detect-progress (Step 10 entry).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-16 16:01:43 +03:00
parent fa38bfe608
commit d7a17a8248
2 changed files with 16 additions and 5 deletions
+13 -2
View File
@@ -17,7 +17,7 @@ Bootstrap reference: `_docs/02_tasks/todo/AZ-263_initial_structure.md`. Architec
4. Native (C++) libraries live under `cpp/` (parallel to `src/`, NOT nested), built by CMake; per-component pybind11 wrappers live at `src/gps_denied_onboard/components/<component>/_native/<name>.py` and import the resulting `.so` from a CMake-known path.
5. **Public API surface per component** = the files listed in each component's `Public API` list below. Anything not listed is internal and MUST NOT be imported from another component.
6. The composition root is `src/gps_denied_onboard/runtime_root.py`. It is the ONLY place that may import concrete strategy implementations across components — every other cross-component dependency is constructor-injected against an interface (ADR-009).
7. Tests mirror the component graph 1:1 at `tests/unit/<component>/`. Cross-component scenarios live in `tests/integration/`, `tests/e2e/`, `tests/perf/`, `tests/security/`, `tests/resilience/`.
7. Tests mirror the component graph 1:1 at `tests/unit/<component>/`. In-process cross-component scenarios that import SUT source live under `tests/integration/`. The **blackbox / e2e** test harness — which MUST NOT import SUT source and exercises the system only via public boundaries (MAVLink / MSP2 / HTTP / filesystem) — lives at the repo-root `e2e/` directory and is owned by the `blackbox_tests` cross-cutting entry (Shared section). Performance, resilience, security, and resource-limit scenarios that are also boundary-driven likewise live under `e2e/tests/<category>/`; only in-process performance/security micro-tests (if any) would live under `tests/perf/`, `tests/security/`, `tests/resilience/`.
8. Build-time exclusion (ADR-002): each `<component>/_native/` and the corresponding `cpp/<lib>/` carry a CMake `BUILD_<NAME>` flag. The composition root validator refuses to wire a strategy whose flag is OFF.
9. **AZ-507 cross-component contract surface** — the only places a `components/<X>/*.py` file may import are: its own subpackage (`gps_denied_onboard.components.<X>.*`), `_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` (interface only). Cross-component contracts (Protocols + typed exceptions) reach consumers through `_types/*` modules — DTOs in the canonical `_types` files (e.g. `_types.inference.EngineCacheEntry`), typed-error envelopes in `_types.inference_errors`, and consumer-side structural `Protocol` cuts defined locally inside each consuming component (e.g. `c10_provisioning.engine_compiler.CompileEngineCallable`). NEVER `from gps_denied_onboard.components.<other_component> import ...` — the AZ-270 `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies` lint enforces this on every `components/**/*.py`. The composition root (`runtime_root/*`) is the single exception; it wires concrete strategies into duck-typed Protocol parameters via constructor injection. This rule is the architectural contract paired with the AZ-270 lint; see `architecture.md` § Cross-Component Contract Surface for the rationale.
@@ -416,6 +416,17 @@ Bootstrap reference: `_docs/02_tasks/todo/AZ-263_initial_structure.md`. Architec
- **Owned by**: AZ-263.
- **Consumed by**: companion-tier1 Dockerfile, operator-orchestrator Dockerfile, CI smoke job.
### blackbox_tests (cross-cutting test harness)
- **Directory**: `e2e/` (repo root, **NOT** under `tests/`)
- **Purpose**: Tier-1 Docker + Tier-2 Jetson blackbox test harness. The runner image is fully separated from the SUT and exercises the system through declared public boundaries only (frame source replay, FC inbound/outbound via SITL, tile-cache mount, MAVLink via mavproxy, FDR filesystem, mock Suite Sat Service). Owns the docker-compose test environment, Jetson Tier-2 runner scripts, fixture builders, runner image, conftest, pytest plugins (csv reporter, evidence bundler), helper modules, and per-category test trees (`positive/`, `negative/`, `performance/`, `resilience/`, `security/`, `resource_limit/`).
- **Owned by**: epic AZ-262 (E-BBT) — task specs AZ-406 (infrastructure bootstrap), AZ-407..AZ-446 (fixture builders + per-scenario tests + Tier-2 harness wrapper + CSV reporter).
- **Owns (exclusive write during implementation)**: `e2e/**`
- **Imports from**: nothing inside `src/gps_denied_onboard/**`. The runner image MUST NOT import any SUT module; the only legal interaction surfaces are MAVLink / MSP2 / HTTP / filesystem. Reads RO from `_docs/00_problem/input_data/**` (bind-mounted test data) and `_docs/02_document/tests/**` (test specs that drive AC mapping). May import standard ground-side libraries (`pymavlink`, `opencv-python`, `numpy`, `scipy`, `geopy`, `pytest`, etc.) and the `msp_gps_toy` Rust binary via subprocess.
- **FORBIDDEN**: `src/gps_denied_onboard/**` (any product source), `tests/unit/**`, `tests/integration/**`, `cpp/**` (native source trees), `db/migrations/**`. Product-side tests under `tests/unit/<component>/` remain owned by the respective component per its existing Per-Component Mapping entry.
- **Consumed by**: CI matrix (Tier-1 docker-compose entrypoint, Tier-2 Jetson runner harness); operator manual Tier-2 invocation via `./e2e/jetson/run-tier2.sh`.
- **Layering note**: blackbox_tests is an external observer of the SUT — it does not sit in the production layering table. Treat it as a separate harness outside Layers 15. The "no Layer-3 → Layer-4 imports" and "interface-at-producer" rules do not apply (no production code lives here).
## Allowed Dependencies (Layering)
Read top-to-bottom; an upper layer may import from a lower layer but NEVER the reverse. Cross-layer violations are **Architecture** findings in code-review (High severity).
@@ -477,7 +488,7 @@ Build-time exclusion is enforced by:
## Self-Verification Checklist
- [x] Every component in `_docs/02_document/components/` has a Per-Component Mapping entry (14 components: 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_orchestrator, c13_fdr).
- [x] Every shared / cross-cutting concern has a Shared section entry (_types, config, logging, fdr_client, frame_source, clock, replay_input, helpers/* × 8, runtime_root, cli/replay, healthcheck).
- [x] Every shared / cross-cutting concern has a Shared section entry (_types, config, logging, fdr_client, frame_source, clock, replay_input, helpers/* × 8, runtime_root, cli/replay, healthcheck, blackbox_tests).
- [x] Layering table covers every component; foundation at Layer 1.
- [x] No component's `Imports from` list points at a component in a higher layer (back-channel exception for C8 → C1/C5 documented as interface-at-producer pattern).
- [x] Paths follow Python `src/`-layout convention with single top-level package `gps_denied_onboard/`.
+3 -3
View File
@@ -4,10 +4,10 @@
flow: greenfield
step: 10
name: Implement Tests
status: not_started
status: in_progress
sub_step:
phase: 0
name: awaiting-invocation
phase: 1
name: parse-and-detect-progress
detail: ""
retry_count: 0
cycle: 1