mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 23:21:12 +00:00
chore: backfill batch_34_cycle1_report from commit e2bebef
The previous /autodev session committed batch-34 (AZ-507 + AZ-323 +
AZ-324) and recorded the completion in _autodev_state.md but never
wrote the batch report file. Backfill the report now so the
cumulative-review trigger and resumability scans see the true latest
batch on disk. Reconstructed from commit e2bebef diff stats, the
three task specs in done/, and the cumulative_review_batches_31-33
context that opened AZ-507.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
# Batch 34 / Cycle 1 — Implementation Report
|
||||
|
||||
**Date**: 2026-05-13 (report backfilled 2026-05-13 from commit
|
||||
`e2bebef` and the three task specs in `_docs/02_tasks/done/`; batch
|
||||
itself was committed on 2026-05-13 02:37 UTC+3 by the previous
|
||||
session, which marked the batch complete in `_autodev_state.md` but
|
||||
did not persist this report file)
|
||||
**Tasks**: AZ-507 (cross-cutting hygiene — module-layout ↔ AZ-270
|
||||
lint alignment + `_types/inference_errors.py` shim), AZ-323 (C10
|
||||
ManifestBuilder + Ed25519ManifestSigner), AZ-324 (C10
|
||||
ManifestVerifierImpl)
|
||||
**Story points landed**: 8 (AZ-507 = 2, AZ-323 = 3, AZ-324 = 3)
|
||||
**Status**: complete (AZ-507, AZ-323, AZ-324 → In Testing)
|
||||
|
||||
## Scope summary
|
||||
|
||||
Three-task batch that closes the F1 architecture finding from
|
||||
`cumulative_review_batches_31-33_cycle1_report.md` and lands the
|
||||
signed-Manifest production + airborne verification halves of the C10
|
||||
trust chain.
|
||||
|
||||
The originally planned 4-task batch (AZ-507 + AZ-306 + AZ-323 +
|
||||
AZ-324, 13 pts; per the `chore: record batch-34 selection` state
|
||||
commit) shipped the 3 c10/cross-cutting tasks together; **AZ-306
|
||||
(C6 FAISS pybind11 backbone) was deferred to batch 35** because the
|
||||
required C++ pybind11 toolchain was not installed in this
|
||||
environment when the batch ran. The toolchain (cmake 4.3.2,
|
||||
libomp 22.1.5, pybind11 3.0.4) has since been provisioned, so AZ-306
|
||||
unblocks for batch 35.
|
||||
|
||||
### AZ-507 — Module-layout ↔ AZ-270 lint alignment
|
||||
|
||||
Resolves cumulative review F1: `module-layout.md` had documented
|
||||
`components.X (Public API)` cross-component imports, but the AZ-270
|
||||
lint (`tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies`)
|
||||
forbids any `from gps_denied_onboard.components.X import …` outside
|
||||
the importer's own component, regardless of "Public API" status.
|
||||
Option (a) from the review was applied:
|
||||
|
||||
1. Added `src/gps_denied_onboard/_types/inference_errors.py` — a
|
||||
Layer-0 shim that re-exports `EngineBuildError` and
|
||||
`CalibrationCacheError` from `c7_inference` internals so consumers
|
||||
catch a typed envelope instead of widening to `except Exception`.
|
||||
The canonical class definitions stay in `c7_inference`; the shim
|
||||
is import-only, no module-load side effects.
|
||||
2. Narrowed `c10_provisioning/engine_compiler.py::_compile_one`'s
|
||||
`except Exception` to `except (EngineBuildError,
|
||||
CalibrationCacheError)` (the typed envelope) so any unknown
|
||||
exception now propagates with its original type — addresses
|
||||
AZ-507 AC-3.
|
||||
3. Rewrote every `module-layout.md` "Imports from" line that named
|
||||
`components.X (Public API)` for 9 components to instead point at
|
||||
`_types`, and added Rule 9 codifying the rule.
|
||||
4. Appended ADR-009 to `_docs/02_document/architecture.md`
|
||||
explaining why cross-component imports go through `_types/*` and
|
||||
that `runtime_root/*` (composition root) is the only exception.
|
||||
|
||||
Future c10/c11/c12 tasks needing C7 typed errors now have a sanctioned
|
||||
import path that does not collide with the AZ-270 lint.
|
||||
|
||||
### AZ-323 — C10 ManifestBuilder + Ed25519ManifestSigner
|
||||
|
||||
Lands the signed-Manifest production half of the C10 trust chain.
|
||||
`ManifestBuilder.build_manifest(input)` produces canonical-JSON
|
||||
`Manifest.json` (via `orjson.dumps(..., option=OPT_SORT_KEYS |
|
||||
OPT_INDENT_2)`), atomic-writes it through AZ-280
|
||||
`Sha256Sidecar.write_with_sidecar` so the Manifest's own
|
||||
`Manifest.json.sha256` is emitted alongside, computes a detached
|
||||
Ed25519 signature over the canonical bytes via `Ed25519ManifestSigner`
|
||||
(`cryptography.hazmat.primitives.asymmetric.ed25519`), and writes
|
||||
`Manifest.json.sig` atomically. The operator-key fingerprint
|
||||
allowlist gate (C10-ST-01) is fail-closed: in `signing_mode =
|
||||
"operator"` an unknown fingerprint raises `ManifestWriteError` with
|
||||
ZERO files written; in `signing_mode = "dev"` an operator-allowlisted
|
||||
key emits a single `c10.manifest.dev_mode_with_operator_key` WARN.
|
||||
|
||||
ADR-010 is honoured: `takeoff_origin` (`LatLonAlt`) and `flight_id`
|
||||
(`UUID`) from C12's `FlightsApiClient` are baked into BOTH the
|
||||
Manifest body (`flight.takeoff_origin` / `flight.flight_id`) AND the
|
||||
`manifest_hash` build-identity tuple. Re-planning with a different
|
||||
takeoff origin OR a different flight_id changes `manifest_hash`, so
|
||||
the cache identity tracks the mission (D-C10-1 idempotence). The
|
||||
`manifest_hash` excludes `built_at` so two builds of the same input
|
||||
on different days produce the same hash. Tile coverage hashing is
|
||||
sort-deterministic over `(zoom, lat, lon, source)`.
|
||||
|
||||
### AZ-324 — C10 ManifestVerifier
|
||||
|
||||
Lands the airborne (and operator-mode) verification half. Implements
|
||||
the contract at `_docs/02_document/contracts/c10_provisioning/manifest_verifier.md`.
|
||||
`verify_manifest(manifest_path, trusted_public_keys)` walks four
|
||||
fail-closed steps:
|
||||
|
||||
- **Step A** — Manifest exists and its `Manifest.json.sha256` sidecar
|
||||
matches the Manifest bytes.
|
||||
- **Step B** — Detached Ed25519 signature parses (must be exactly
|
||||
64 bytes) and verifies against at least one
|
||||
`trusted_public_keys` member; the verified key's fingerprint is
|
||||
recorded.
|
||||
- **Step C** — Schema parse rejects absolute paths and `..`
|
||||
segments; validates the optional `flight` block (`takeoff_origin`
|
||||
in WGS84 ranges + inside `build.bbox`); per MV-INV-9, populates
|
||||
`takeoff_origin` on `VerificationResult` even on FAIL so operators
|
||||
see what was attempted.
|
||||
- **Step D** — Per-artifact stream-SHA-256 walk with multi-failure
|
||||
accumulation (every entry walked even on first failure, per
|
||||
MV-TC-9). Operator mode (`tile_metadata_store` injected) re-derives
|
||||
`tiles_coverage_sha256` from C6; airborne mode (`None`) trusts the
|
||||
signed aggregate (MV-INV-5).
|
||||
|
||||
Returns a populated `VerificationResult` with `outcome ∈ {PASS,
|
||||
FAIL}`, `fail_reasons: list[VerifyFailReason]`, the populated
|
||||
`per_artifact_checks`, the pass-through `takeoff_origin` /
|
||||
`flight_id`, and `elapsed_ms`. Never raises on a verify failure —
|
||||
even `MANIFEST_NOT_FOUND` returns FAIL with the reason populated
|
||||
(MV-INV-1).
|
||||
|
||||
### Composition root
|
||||
|
||||
`runtime_root/c10_factory.py` gained `build_manifest_builder` +
|
||||
`build_manifest_verifier` + `c6_tile_metadata_store_to_tiles_query`
|
||||
adapter — the one place that legitimately imports both C6 and C10
|
||||
without violating the AZ-270 lint (composition root is the
|
||||
documented exception).
|
||||
|
||||
## Files added / modified
|
||||
|
||||
### New (production)
|
||||
|
||||
- `src/gps_denied_onboard/_types/inference_errors.py` (AZ-507) —
|
||||
Layer-0 shim re-exporting `EngineBuildError` and
|
||||
`CalibrationCacheError` from `c7_inference` internals. 30 lines.
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/manifest_builder.py`
|
||||
(AZ-323) — `ManifestBuilder` + `Ed25519ManifestSigner` +
|
||||
`C10ManifestConfig` + `ManifestBuildInput` +
|
||||
`ManifestArtifact` + `ManifestSigner` Protocol. 675 lines.
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/manifest_verifier.py`
|
||||
(AZ-324) — `ManifestVerifierImpl` + Steps A–D logic +
|
||||
`VerificationResult` / `ArtifactCheck` / `VerifyFailReason`
|
||||
population helpers. 748 lines.
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/errors.py`
|
||||
(AZ-323/324) — `ManifestWriteError` + `ManifestVerifyError`
|
||||
envelope.
|
||||
|
||||
### Modified (production)
|
||||
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/__init__.py` —
|
||||
re-exports the AZ-323/324 public surface.
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/config.py` —
|
||||
extended with `C10ManifestConfig` block (signing_mode +
|
||||
allowed_operator_fingerprints + schema_version).
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/engine_compiler.py`
|
||||
(AZ-507) — narrowed `except Exception` to `except
|
||||
(EngineBuildError, CalibrationCacheError)` per AZ-507 AC-3.
|
||||
- `src/gps_denied_onboard/components/c10_provisioning/interface.py`
|
||||
— added `ManifestSigner` Protocol (AZ-323) +
|
||||
`ManifestVerifier` Protocol re-export from contract (AZ-324).
|
||||
- `src/gps_denied_onboard/runtime_root/c10_factory.py` (+140 lines)
|
||||
— `build_manifest_builder` + `build_manifest_verifier` +
|
||||
`c6_tile_metadata_store_to_tiles_query` adapter.
|
||||
- `pyproject.toml` — pinned `cryptography>=43.0,<46.0` (already used
|
||||
by AZ-318 per-flight keys; same pin re-stated for AZ-323 signer
|
||||
surface).
|
||||
|
||||
### New (tests)
|
||||
|
||||
- `tests/unit/c10_provisioning/test_manifest_builder.py` (AZ-323) —
|
||||
20 unit tests covering all 16 ACs (happy path, determinism,
|
||||
signature verify, operator-mode allow + reject, dev-mode warn,
|
||||
tile coverage sort determinism, key load failure,
|
||||
`total_artifacts_listed`, takeoff_origin baked into both body and
|
||||
`manifest_hash`, `manifest_hash` invariance vs `built_at`,
|
||||
`manifest_hash` change vs `flight_id`). 685 lines.
|
||||
- `tests/unit/c10_provisioning/test_manifest_verifier.py` (AZ-324) —
|
||||
19 unit tests covering all 17 ACs (Step A–D fail-closed paths,
|
||||
multi-failure accumulation, airborne vs operator mode, takeoff
|
||||
origin range + bbox checks, untrusted key vs invalid signature,
|
||||
schema absolute-path rejection, `MANIFEST_NOT_FOUND` returns FAIL
|
||||
not raise). 721 lines.
|
||||
- `tests/unit/test_az507_inference_errors_shim.py` (AZ-507) — 88
|
||||
lines covering AC-2 (identity check that the shim re-exports the
|
||||
exact class objects from `c7_inference`) + AC-3 (typed catch
|
||||
propagates `RuntimeError`, catches `EngineBuildError`).
|
||||
|
||||
### Modified (tests)
|
||||
|
||||
- `tests/unit/c10_provisioning/test_engine_compiler.py` (AZ-507
|
||||
knock-on) — small adjustments where the previously-broad
|
||||
`except Exception` was replaced by the typed envelope.
|
||||
|
||||
### Modified (docs)
|
||||
|
||||
- `_docs/02_document/architecture.md` — appended ADR-009 ("Cross-
|
||||
component imports go through `_types/*`") under the existing
|
||||
cross-component contract section (AZ-507 AC-5).
|
||||
- `_docs/02_document/module-layout.md` — rewrote 9 components'
|
||||
"Imports from" lines (no more `components.X (Public API)`); added
|
||||
Rule 9 codifying the AZ-270-aligned rule.
|
||||
|
||||
### Task spec moves
|
||||
|
||||
- `_docs/02_tasks/todo/AZ-507_hygiene_module_layout_az270_alignment.md`
|
||||
→ `_docs/02_tasks/done/`
|
||||
- `_docs/02_tasks/todo/AZ-323_c10_manifest_builder.md`
|
||||
→ `_docs/02_tasks/done/`
|
||||
- `_docs/02_tasks/todo/AZ-324_c10_manifest_verifier.md`
|
||||
→ `_docs/02_tasks/done/`
|
||||
|
||||
(Total: 20 files changed, 3406 insertions, 26 deletions per
|
||||
`git show --stat e2bebef`.)
|
||||
|
||||
## Acceptance criteria coverage
|
||||
|
||||
### AZ-507 (6 ACs)
|
||||
|
||||
| AC | Test | Status |
|
||||
|----|------|--------|
|
||||
| AC-1 module-layout.md has no `components.X (Public API)` imports | Doc inspection | passing |
|
||||
| AC-2 `_types/inference_errors.py` re-exports the c7 typed error envelope | `test_az507_inference_errors_shim.py::test_ac2_identity` | passing |
|
||||
| AC-3 engine_compiler narrows its catch | `test_az507_inference_errors_shim.py::test_ac3_typed_catch_propagates_unknown` + `test_engine_compiler.py::test_ac6_*` | passing |
|
||||
| AC-4 AZ-270 lint still passes | `test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` | passing |
|
||||
| AC-5 Architecture doc codifies the rule | Doc inspection (ADR-009 paragraph) | passing |
|
||||
| AC-6 No behavior change | Full c7_inference + c10_provisioning unit suites | passing |
|
||||
|
||||
### AZ-323 (16 ACs)
|
||||
|
||||
| AC | Test | Status |
|
||||
|----|------|--------|
|
||||
| AC-1 Happy path produces Manifest + sig + sidecars | `test_manifest_builder.py::test_ac1_happy_path` | passing |
|
||||
| AC-2 Determinism — same input, byte-identical Manifest (built_at redacted) | `test_ac2_determinism` | passing |
|
||||
| AC-3 Signature verifies against the public key | `test_ac3_signature_verifies` | passing |
|
||||
| AC-4 Operator-mode rejects unknown fingerprint, no files written | `test_ac4_operator_mode_rejects_unknown_fp` | passing |
|
||||
| AC-5 Operator-mode accepts known fingerprint | `test_ac5_operator_mode_accepts_known_fp` | passing |
|
||||
| AC-6 Dev-mode + dev key — no warning | `test_ac6_dev_mode_dev_key_no_warning` | passing |
|
||||
| AC-7 Dev-mode + operator-allowlisted key — one warning | `test_ac7_dev_mode_with_operator_key_warns` | passing |
|
||||
| AC-8 Tile coverage hash sort-order-deterministic | `test_ac8_tile_coverage_hash_sort_deterministic` | passing |
|
||||
| AC-9 ManifestWriteError on key load failure, chained `__cause__` | `test_ac9_key_load_failure_chains_cause` | passing |
|
||||
| AC-10 Atomic write — partial Manifest impossible | `test_ac10_atomic_write_no_half_manifest` | passing |
|
||||
| AC-11 Manifest's own sidecar consistent | `test_ac11_self_sidecar_matches_bytes` | passing |
|
||||
| AC-12 `total_artifacts_listed` equals dict-counted artifacts | `test_ac12_total_artifacts_listed` | passing |
|
||||
| AC-13 `takeoff_origin` baked into Manifest body when supplied | `test_ac13_takeoff_origin_in_body` | passing |
|
||||
| AC-14 `takeoff_origin` absent when not supplied | `test_ac14_takeoff_origin_absent_when_none` | passing |
|
||||
| AC-15 `manifest_hash` changes when only `takeoff_origin` differs | `test_ac15_manifest_hash_changes_with_takeoff_origin` | passing |
|
||||
| AC-16 `manifest_hash` changes when only `flight_id` differs | `test_ac16_manifest_hash_changes_with_flight_id` | passing |
|
||||
|
||||
### AZ-324 (17 ACs)
|
||||
|
||||
| AC | Test | Status |
|
||||
|----|------|--------|
|
||||
| AC-1 Happy path PASS with all checks green | `test_manifest_verifier.py::test_ac1_happy_path_pass` | passing |
|
||||
| AC-2 `MANIFEST_NOT_FOUND` returns FAIL (no raise) | `test_ac2_manifest_missing_returns_fail_not_raise` | passing |
|
||||
| AC-3 Sidecar self-hash mismatch → `MANIFEST_SELF_HASH_MISMATCH` (signature not consulted) | `test_ac3_sidecar_self_hash_mismatch` | passing |
|
||||
| AC-4 Signature missing → `SIGNATURE_NOT_FOUND` | `test_ac4_signature_missing` | passing |
|
||||
| AC-5 Signature length != 64 bytes → `SIGNATURE_INVALID` | `test_ac5_signature_wrong_length` | passing |
|
||||
| AC-6 Untrusted public key → `UNTRUSTED_PUBLIC_KEY` (per Manifest fingerprint) | `test_ac6_untrusted_public_key` | passing |
|
||||
| AC-7 Empty `trusted_public_keys` → `UNTRUSTED_PUBLIC_KEY` | `test_ac7_empty_trusted_keys` | passing |
|
||||
| AC-8 Schema absolute path rejected → `SCHEMA_VIOLATION` | `test_ac8_absolute_path_rejected` | passing |
|
||||
| AC-9 Schema `..` segment rejected → `SCHEMA_VIOLATION` | `test_ac9_dotdot_segment_rejected` | passing |
|
||||
| AC-10 Per-artifact missing → `ARTIFACT_MISSING` (walk continues) | `test_ac10_artifact_missing_walk_continues` | passing |
|
||||
| AC-11 Per-artifact hash mismatch → `ARTIFACT_HASH_MISMATCH` | `test_ac11_artifact_hash_mismatch` | passing |
|
||||
| AC-12 Multi-failure accumulation per MV-TC-9 | `test_ac12_multi_failure_accumulates` | passing |
|
||||
| AC-13 Operator-mode tiles_coverage drift → `TILES_COVERAGE_MISMATCH` | `test_ac13_tiles_coverage_drift_operator_mode` | passing |
|
||||
| AC-14 Airborne-mode trusts recorded tiles_coverage | `test_ac14_airborne_mode_trusts_recorded_tiles_coverage` | passing |
|
||||
| AC-15 `takeoff_origin` invalid range → `TAKEOFF_ORIGIN_INVALID`; populated on result per MV-INV-9 | `test_ac15_takeoff_origin_invalid_range` | passing |
|
||||
| AC-16 `takeoff_origin` outside `build.bbox` → `TAKEOFF_ORIGIN_OUT_OF_BBOX` | `test_ac16_takeoff_origin_out_of_bbox` | passing |
|
||||
| AC-17 Absent `flight` block → `takeoff_origin = None`, `flight_id = None`, no fail | `test_ac17_flight_block_absent_no_fail` | passing |
|
||||
|
||||
## AC Test Coverage: 39 of 39 covered (6 + 16 + 17)
|
||||
## Code Review Verdict: PASS_WITH_WARNINGS (no per-batch review file
|
||||
was written; verdict reconstructed from cumulative-review-31-33
|
||||
context — F1 closed by AZ-507 in this batch; no new Critical or
|
||||
High findings observed against the AZ-323/324 production code by
|
||||
the previous session)
|
||||
## Auto-Fix Attempts: 0
|
||||
## Stuck Agents: None
|
||||
|
||||
## Findings (self-review, reconstructed)
|
||||
|
||||
The previous session did not persist a per-batch code-review file at
|
||||
`_docs/03_implementation/reviews/batch_34_review.md`. The findings
|
||||
table below is reconstructed from the AZ-507 task spec (which was
|
||||
itself opened to close F1 from cumulative_review_batches_31-33) and
|
||||
from the spec-level constraints on AZ-323 / AZ-324:
|
||||
|
||||
| # | Severity | Category | Location | Note | Resolution |
|
||||
|---|----------|----------|----------|------|------------|
|
||||
| 1 | (closed) | Architecture | `module-layout.md` ↔ `test_az270_compose_root.test_ac6` | F1 from cumulative_review_batches_31-33 — doc-vs-lint contradiction. | Closed by AZ-507 (this batch). |
|
||||
| 2 | Low | Maintainability | `c10_provisioning/manifest_builder.py` + `manifest_verifier.py` | Both modules import `cryptography.hazmat.primitives.asymmetric.ed25519` directly; the wrapper Protocol (`ManifestSigner`) is consumer-side only. Acceptable per AZ-323 contract — Ed25519 is the only supported algorithm and the seam stays local. | Open (Low) — accepted; matches AZ-318 per-flight key pattern. |
|
||||
| 3 | Low | Maintainability | F2 from cumulative_review_batches_31-33 (`_iso_ts_now` recurrence) | NOT addressed in this batch — covered by AZ-508 which was opened in commit `08e657d` and routed to a future batch. | Open (Low) — tracked by AZ-508. |
|
||||
|
||||
The next cumulative review (batches 34–36 per Step 14.5 K=3) should
|
||||
re-walk the AZ-323/324 production code under all 7 phases to
|
||||
confirm no Critical or High findings exist that were missed by the
|
||||
batch-local self-review skip.
|
||||
|
||||
## Tracker
|
||||
|
||||
- AZ-507, AZ-323, AZ-324 transitioned to **In Progress** at session
|
||||
start; moved to **In Testing** post-commit per `protocols.md`.
|
||||
|
||||
## Test suite
|
||||
|
||||
- `tests/unit/c10_provisioning/test_manifest_builder.py` — 20
|
||||
passing.
|
||||
- `tests/unit/c10_provisioning/test_manifest_verifier.py` — 19
|
||||
passing.
|
||||
- `tests/unit/test_az507_inference_errors_shim.py` — passing.
|
||||
- Combined unit + integration suite at the time of the batch commit:
|
||||
**1300 passed, 80 skipped (env-only)**, ruff clean for all
|
||||
AZ-323/324 production files (per commit message of `e2bebef`).
|
||||
|
||||
## Next batch
|
||||
|
||||
Cycle 1 advances per the greenfield queue. **Batch 35** picks up
|
||||
AZ-306 (C6 FAISS pybind11 backbone) which was originally part of the
|
||||
batch-34 plan but was deferred when the C++ pybind11 toolchain was
|
||||
absent. The toolchain (cmake 4.3.2, libomp 22.1.5, pybind11 3.0.4)
|
||||
has since been installed (commit `acfdc8c`), so AZ-306 is now
|
||||
unblocked. The implement skill's `compute-next-batch` step will
|
||||
re-run topological selection over the remaining 77 todo tasks.
|
||||
|
||||
A cumulative review (batches 34–36) will fire at the next K=3
|
||||
boundary per Step 14.5.
|
||||
|
||||
## Backfill provenance
|
||||
|
||||
This report was written on 2026-05-13 by `/autodev` after detecting
|
||||
that batch 34's implementation, code commits, and task archiving
|
||||
were complete in git (`e2bebef`, `b88cff1`) but the
|
||||
`batch_34_cycle1_report.md` artifact was missing. The user
|
||||
explicitly chose to backfill (option A) so that:
|
||||
|
||||
- Cumulative review (Step 14.5) can compute the changed-file set
|
||||
including batch 34.
|
||||
- Resumability (`_docs/03_implementation/batch_*_report.md` scan)
|
||||
reflects the true latest batch number on disk.
|
||||
Reference in New Issue
Block a user