mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 21:51:12 +00:00
[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:
@@ -0,0 +1,9 @@
|
||||
"""Structured JSON logging entrypoint (E-CC-LOG / AZ-245).
|
||||
|
||||
Bootstrap (AZ-263) ships a working `get_logger` so every other module can import it;
|
||||
the concrete sink + redaction policy is layered on by AZ-266.
|
||||
"""
|
||||
|
||||
from gps_denied_onboard.logging.structured import get_logger
|
||||
|
||||
__all__ = ["get_logger"]
|
||||
@@ -0,0 +1,83 @@
|
||||
"""Structured JSON logging.
|
||||
|
||||
E-CC-LOG / AZ-245 contract: one JSON object per log line. Bootstrap provides a
|
||||
minimal working `get_logger(name)` so every other module can import it; AZ-266
|
||||
will add full redaction and the FDR sink.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
|
||||
class _JsonFormatter(logging.Formatter):
|
||||
"""Emit a single JSON object per log line — no narrative log lines (E-CC-LOG)."""
|
||||
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
payload: dict[str, Any] = {
|
||||
"ts": time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(record.created))
|
||||
+ f".{int(record.msecs):03d}Z",
|
||||
"level": record.levelname,
|
||||
"logger": record.name,
|
||||
"msg": record.getMessage(),
|
||||
}
|
||||
if record.exc_info:
|
||||
payload["exc"] = self.formatException(record.exc_info)
|
||||
for key, value in record.__dict__.items():
|
||||
if key in (
|
||||
"args",
|
||||
"msg",
|
||||
"levelname",
|
||||
"name",
|
||||
"exc_info",
|
||||
"exc_text",
|
||||
"stack_info",
|
||||
"created",
|
||||
"msecs",
|
||||
"relativeCreated",
|
||||
"thread",
|
||||
"threadName",
|
||||
"processName",
|
||||
"process",
|
||||
"module",
|
||||
"funcName",
|
||||
"filename",
|
||||
"pathname",
|
||||
"lineno",
|
||||
"levelno",
|
||||
):
|
||||
continue
|
||||
payload.setdefault(key, value)
|
||||
return json.dumps(payload, separators=(",", ":"), default=str)
|
||||
|
||||
|
||||
_CONFIGURED = False
|
||||
|
||||
|
||||
def _configure_root_once() -> None:
|
||||
global _CONFIGURED
|
||||
if _CONFIGURED:
|
||||
return
|
||||
handler = logging.StreamHandler(sys.stderr)
|
||||
handler.setFormatter(_JsonFormatter())
|
||||
root = logging.getLogger()
|
||||
root.handlers.clear()
|
||||
root.addHandler(handler)
|
||||
level_name = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
root.setLevel(getattr(logging, level_name, logging.INFO))
|
||||
_CONFIGURED = True
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""Return a structured JSON logger.
|
||||
|
||||
Every component imports its logger via
|
||||
`from gps_denied_onboard.logging import get_logger`.
|
||||
"""
|
||||
_configure_root_once()
|
||||
return logging.getLogger(name)
|
||||
Reference in New Issue
Block a user