mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 00:31:14 +00:00
Decompose Step 6 snapshot: 140 task specs + contract docs
Closes out greenfield Step 6 (Decompose) for all 14 components (C1-C13 + cross-cutting helpers/replay). Covers tasks AZ-266..AZ-446 plus the _dependencies_table.md and component contract documents. State file updated to greenfield Step 7 (Implement), not_started. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Contract: log_record_schema
|
||||
|
||||
**Component**: shared_logging (cross-cutting concern owned by E-CC-LOG / AZ-245)
|
||||
**Producer task**: AZ-266 — `_docs/02_tasks/todo/AZ-266_log_module.md`
|
||||
**Consumer tasks**: every component task that emits logs (C1–C13 components, plus C12 operator tooling)
|
||||
**Version**: 1.0.0
|
||||
**Status**: draft
|
||||
**Last Updated**: 2026-05-10
|
||||
|
||||
## Purpose
|
||||
|
||||
Frozen, machine-parseable JSON envelope for every log record emitted by any onboard component. Stable field set + ordering is a hard requirement for FDR analysis tooling (`kind="log"` records are post-flight queryable) and for the contract test that verifies field-name + ordering invariants.
|
||||
|
||||
## Shape
|
||||
|
||||
### One JSON object per log line, UTF-8, no trailing comma, newline-terminated
|
||||
|
||||
```python
|
||||
# Conceptual dataclass — actual implementation may emit via orjson / python-json-logger
|
||||
@frozen
|
||||
class LogRecord:
|
||||
ts: str # ISO 8601 UTC, microsecond precision, e.g. "2026-05-10T03:14:15.123456Z"
|
||||
level: str # one of {"DEBUG", "INFO", "WARN", "ERROR"} — matches Python stdlib levelname (no "WARNING")
|
||||
component: str # component slug from module-layout.md, e.g. "c2_vpr", "c5_state", "shared.logging"
|
||||
frame_id: int | None # monotonic per-flight frame counter; None for non-frame-correlated records (startup, shutdown, periodic)
|
||||
kind: str # categorical tag, e.g. "vio.tick", "vpr.query", "fdr.write", "log.diag"
|
||||
msg: str # human-readable short message, no PII, no stack traces (those go in `exc`)
|
||||
kv: dict[str, Any] # arbitrary structured key-value payload, JSON-safe scalars + nested dict/list only
|
||||
exc: str | None # optional formatted exception traceback for ERROR/WARN; None otherwise
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description | Constraints |
|
||||
|-------|------|----------|-------------|-------------|
|
||||
| `ts` | string (ISO 8601 UTC, µs) | yes | Emit timestamp | RFC 3339 with `Z` suffix |
|
||||
| `level` | string | yes | Log level | strictly one of `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||||
| `component` | string | yes | Origin component slug | snake_case, must match a module-layout entry or `shared.<name>` |
|
||||
| `frame_id` | integer or null | no | Per-flight monotonic frame index | non-negative when present |
|
||||
| `kind` | string | yes | Record category tag | dotted snake_case, max 64 chars |
|
||||
| `msg` | string | yes | Human message | no embedded newlines (use `kv` for multi-line context) |
|
||||
| `kv` | object | yes (may be `{}`) | Structured key-value payload | JSON-safe scalars + nested objects/arrays |
|
||||
| `exc` | string or null | no | Exception traceback for ERROR/WARN | absent or `null` for INFO/DEBUG |
|
||||
|
||||
### Field ordering (REQUIRED — verified by contract test)
|
||||
|
||||
`ts, level, component, frame_id, kind, msg, kv, exc` — formatter MUST emit keys in this order. Re-ordering breaks downstream column-aligned parsers used by FDR tooling.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Every record is a single JSON object on a single line (newline-terminated, no embedded newlines in any field value).
|
||||
- `level` value uses `WARN` not `WARNING` (intentional, simpler grep target).
|
||||
- `frame_id` is omitted (`null`) — never invented — when the emitter has no current frame context.
|
||||
- `kv` values must be JSON-serialisable without custom encoders; binary payloads are base64-encoded strings within `kv`.
|
||||
- `exc` is present only for `level in {WARN, ERROR}` records that originated from an exception; otherwise it is `null` or absent.
|
||||
- The schema is strictly additive — no field is ever removed or renamed without a major version bump and a matching FDR record-schema migration in E-CC-FDR-CLIENT.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- This contract does not define WHAT to log (per-component § 9 sections own that).
|
||||
- This contract does not define log routing (stdout vs journald vs FDR — see handler topology in E-CC-LOG epic).
|
||||
- This contract does not define structured event types — `kind` is a free-form tag, not a closed enum.
|
||||
|
||||
## Versioning Rules
|
||||
|
||||
- **Breaking changes** (field renamed/removed, type changed, ordering changed, level enum reduced) require a new major version + a deprecation pass through every consumer.
|
||||
- **Non-breaking additions** (new optional field appended at the end of the order, new `kind` tag, new `level` value) require a minor version bump.
|
||||
- The contract test (`tests/contract/log_schema.py`) MUST be updated alongside any version bump.
|
||||
|
||||
## Test Cases
|
||||
|
||||
| Case | Input | Expected | Notes |
|
||||
|------|-------|----------|-------|
|
||||
| valid-info-no-frame | `level=INFO, component="c2_vpr", kind="vpr.warmup", msg="loaded model", kv={"model": "salad"}` | accepted; `frame_id=null`, `exc=null`; field order matches spec | Startup-time INFO record |
|
||||
| valid-warn-with-frame | `level=WARN, component="c5_state", frame_id=4321, kind="state.cov_spike", msg="covariance jumped 5x", kv={"jump_factor": 5.2}` | accepted; key order locked; FDR bridge MUST forward this record | Cross-cuts AC: WARN flows into FDR |
|
||||
| valid-error-with-exc | `level=ERROR, component="c11_tilemanager", kind="tile.upload_fail", msg="HTTP 503", kv={"tile": "z18/x12345/y67890"}, exc="Traceback (most recent call last):..."` | accepted; `exc` present and non-null; FDR bridge MUST forward | Cross-cuts AC: ERROR + exc captured |
|
||||
| invalid-bad-level | `level="WARNING"` | rejected with `LogSchemaError` (or formatter logs at ERROR and drops record) | Contract test enforces `WARN` not `WARNING` |
|
||||
| invalid-multiline-msg | `msg="line1\nline2"` | rejected OR newline replaced with `\\n` literal — formatter must guarantee single-line output | One JSON object per line invariant |
|
||||
| invalid-non-serialisable-kv | `kv={"obj": <numpy.ndarray>}` | rejected with `LogSchemaError` (caller must convert to list before passing) | JSON-safe-only invariant |
|
||||
| ordering-stable | any valid record | emitted JSON keys appear in `ts, level, component, frame_id, kind, msg, kv, exc` order regardless of construction order | Contract test parses raw bytes and asserts key order |
|
||||
|
||||
## Change Log
|
||||
|
||||
| Version | Date | Change | Author |
|
||||
|---------|------|--------|--------|
|
||||
| 1.0.0 | 2026-05-10 | Initial contract derived from E-CC-LOG epic (AZ-245) | autodev decompose Step 2 |
|
||||
Reference in New Issue
Block a user