# Phase 1 — Inbound telemetry Implement a Node.js TCP server that ingests Teltonika telemetry over codecs 8, 8E, and 16; publishes normalized `Position` records to a Redis Stream; and ships with the operational baseline (Prometheus metrics, fixture-based tests, Dockerfile, Gitea CI/CD pipeline). ## Outcome statement When Phase 1 is done: - Devices in the deployed FMB/FMC/FMM/FMU fleet connect to a known TCP port, complete the IMEI handshake, and stream AVL frames. - Every well-formed AVL record produces exactly one `Position` JSON entry on the `telemetry:teltonika` Redis Stream, with all GPS fields and IO element bag intact. - CRC-mismatched frames are dropped (no ACK) so devices retransmit. - Unknown-codec frames cause the connection to close with a structured `WARN` log entry; a Prometheus counter increments. - **Device authority is observable but permissive by default** — every handshake is labeled `known` or `unknown` based on a configurable `DeviceAuthority`; the Phase 1 default `AllowAllAuthority` accepts everything, and an opt-in `RedisAllowListAuthority` (task 1.13) reads a Directus-published allow-list from Redis. Strict reject-on-unknown is gated behind a `STRICT_DEVICE_AUTH` flag. - The service builds reproducibly via a Gitea Actions workflow, publishing a Docker image to the project's Gitea Container Registry, tagged by branch + git SHA. - Tests cover every codec parser using hex captures sourced from the canonical Teltonika doc, with at least one synthetic edge-case fixture per codec. ## Sequencing ``` 1.1 Project scaffold ├─→ 1.2 Core shell & framing types │ ├─→ 1.3 Configuration & logging │ ├─→ 1.4 Teltonika framing layer (incl. DeviceAuthority seam) │ │ ├─→ 1.5 Codec 8 parser │ │ ├─→ 1.6 Codec 8 Extended parser │ │ └─→ 1.7 Codec 16 parser │ └─→ 1.8 Redis publisher & main wiring │ └─→ 1.10 Observability │ ├─→ 1.11 Dockerfile & CI │ │ └─→ 1.12 Production hardening │ └─→ 1.13 Device authority (opt-in, deferrable) └─→ 1.9 Fixture suite (cross-cutting; established alongside 1.5) ``` Tasks 1.5, 1.6, 1.7 can be done in parallel after 1.4 lands. Task 1.9 (fixture infrastructure) should land *with or before* 1.5 — it's the framework the codec tasks add to. Task 1.13 is the only Phase 1 task that can ship *after* the rest of Phase 1 is in production — `AllowAllAuthority` is functional from day one; the Redis allow-list lights up once the Directus-side publisher exists. ## Files modified Phase 1 produces this layout in `tcp-ingestion/`: ``` tcp-ingestion/ ├── .gitea/workflows/build.yml ├── src/ │ ├── core/ │ │ ├── types.ts │ │ ├── publish.ts │ │ ├── registry.ts │ │ ├── session.ts │ │ └── server.ts │ ├── adapters/ │ │ └── teltonika/ │ │ ├── index.ts │ │ ├── handshake.ts │ │ ├── frame.ts │ │ ├── crc.ts │ │ ├── device-authority.ts (interface + AllowAllAuthority) │ │ ├── redis-allow-list-authority.ts (task 1.13, opt-in) │ │ └── codec/ │ │ ├── data/ │ │ │ ├── codec8.ts │ │ │ ├── codec8e.ts │ │ │ └── codec16.ts │ │ └── command/ (empty in Phase 1) │ ├── config/load.ts │ ├── observability/ │ │ ├── logger.ts │ │ └── metrics.ts │ └── main.ts ├── test/ │ ├── fixtures/teltonika/ │ │ ├── codec8/ │ │ ├── codec8e/ │ │ └── codec16/ │ ├── codec8.test.ts │ ├── codec8e.test.ts │ ├── codec16.test.ts │ ├── crc.test.ts │ └── frame.test.ts ├── Dockerfile ├── package.json ├── pnpm-lock.yaml ├── tsconfig.json ├── .dockerignore ├── .gitignore ├── .prettierrc ├── eslint.config.js └── README.md ``` ## Tech stack (decided) - **Node.js 22 LTS**, ESM-only. - **TypeScript 5.x** with `strict: true`. - **pnpm** for dependency management (deterministic, fast, easy to add workspaces later if needed). - **vitest** for tests. - **pino** for structured logging. - **prom-client** for Prometheus metrics. - **ioredis** for Redis Streams. - **zod** for environment-variable validation. If an implementer wants to deviate, they must update the relevant task file first.