> **Superseded:** This 2026-06-23 split design is historical context.
> Use `docs/target-architecture.md` and
> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the
> current production architecture: TDengine is the default hot history store,
> `sink-archive` owns raw bytes, and `event-file-store` is optional
> compatibility only.

# GB32960 Service Split Design

Date: 2026-06-23

## Goal

Split the current all-in-one vehicle ingest runtime into lower-coupled, higher-cohesion services. The first production slice focuses on GB/T 32960 because it is the active high-pressure path: persistent TCP connections, protocol ACKs, raw frame volume, history queries, and downstream analytics are currently assembled in one `bootstrap-all` process.

The target outcome is:

- Keep GB32960 ingest reliable and fast.
- Move disk writes and history queries out of the protocol process.
- Move statistics and analysis out of the protocol process.
- Use Kafka as the durable boundary between ingest, history, and analytics.
- Preserve `bootstrap-all` during migration as a local development and rollback entry point.

## Decision

Use ACK semantics A:

> A GB32960 frame is ACKed successfully after the protocol service parses/accepts it and writes the required Kafka message(s) successfully.

The protocol service must not wait for raw archive, DuckDB/Parquet writes, snapshot generation, or statistics computation before sending the GB32960 response frame.

## Service Boundaries

### `gb32960-ingest-app`

Purpose: own GB32960 connection handling and Kafka production.

Responsibilities:

- Listen on the GB32960 TCP port.
- Handle platform login, VIN authorization, TLS if enabled, and session state required for protocol responses.
- Decode GB32960 frames and apply vendor extension parsing.
- Produce raw frame records and normalized event records to Kafka.
- ACK successful reports only after Kafka production succeeds.
- Publish malformed or rejected frames to a DLQ when possible.
- Expose operational health and metrics for connections, decode rate, Kafka latency, ACK latency, and DLQ count.

Non-responsibilities:

- No local raw archive writes.
- No DuckDB or Parquet history writes.
- No snapshot query API.
- No vehicle statistics or analysis.
- No long-running analytical calculations.

Initial module dependencies:

- `ingest-api`
- `ingest-core`
- `ingest-codec-common`
- `session-core`
- `protocol-gb32960`
- `sink-kafka`
- `observability`

### `vehicle-history-app`

Purpose: own raw storage, historical index storage, and history query APIs.

Responsibilities:

- Consume GB32960 raw records and normalized event records from Kafka.
- Write raw `.bin` payloads through `sink-archive`.
- Write queryable event indexes through `event-file-store`.
- Provide APIs for raw frame lookup, decoded frame lookup, history query, and snapshot/source-frame lookup.
- Rebuild or backfill the DuckDB sidecar index from stored Parquet files when needed.
- Track consumer lag, write latency, archive failures, index failures, and query latency.

Non-responsibilities:

- No TCP protocol listener.
- No GB32960 ACK decisions.
- No online statistics computation.
- No direct coupling to `gb32960-ingest-app` internals.

Initial module dependencies:

- `ingest-api`
- `sink-kafka`
- `sink-archive`
- `event-file-store`
- `event-history-service`
- `protocol-gb32960` only for raw-frame decode/query support
- `observability`

### `vehicle-analytics-app`

Purpose: own vehicle state, daily statistics, alarms, and later analytics.

Responsibilities:

- Consume normalized vehicle event records from Kafka.
- Maintain latest vehicle state if enabled.
- Compute daily vehicle statistics and alarm-derived metrics.
- Persist analytical outputs to the selected backend.
- Support reprocessing from Kafka offsets when analytical logic changes.
- Track consumer lag, per-VIN processing latency, aggregation failures, and output write latency.

Non-responsibilities:

- No protocol listener.
- No GB32960 ACK decisions.
- No raw archive writes.
- No normal-path dependency on raw archive or history APIs.

Initial module dependencies:

- `ingest-api`
- `sink-kafka`
- `vehicle-state-service`
- `vehicle-stat-service`
- `observability`

## Kafka Contract

Kafka is the service boundary. Messages must be stable enough that `vehicle-history-app` and `vehicle-analytics-app` do not depend on protocol service internals.

### Topics

Use versioned topics:

- `vehicle.raw.gb32960.v1`
- `vehicle.event.gb32960.v1`
- `vehicle.dlq.gb32960.v1`

The current topic names under `lingniu.ingest.sink.kafka.topics` can remain temporarily for compatibility, but the split apps should converge on versioned topic names.

### Keys

Use VIN as the Kafka key whenever a VIN is available.

Benefits:

- Preserves per-vehicle ordering within a partition.
- Lets history and analytics scale by partition.
- Keeps stateful analytics simpler.

When VIN is unavailable, use a stable fallback key such as platform account plus peer address plus receive time bucket.

### Raw Record

Topic: `vehicle.raw.gb32960.v1`

Minimum fields:

- `schemaVersion`
- `protocol = GB32960`
- `eventId`
- `receiveTime`
- `eventTime` if decoded
- `peer`
- `platformAccount`
- `vin` if decoded
- `command`
- `protocolVersion`
- `rawBytes`
- `checksumStatus`
- `decodeStatus`
- `metadata`

Consumers:

- `vehicle-history-app` writes archive `.bin` files and raw-frame query indexes.
- Future replay tools can regenerate normalized events from raw records.

### Normalized Event Record

Topic: `vehicle.event.gb32960.v1`

Minimum fields:

- `schemaVersion`
- `protocol = GB32960`
- `eventId`
- `sourceRawEventId`
- `receiveTime`
- `eventTime`
- `platformAccount`
- `vin`
- `command`
- `eventType`
- `normalizedFields`
- `vendorFields`
- `alarmFields`
- `metadata`

Consumers:

- `vehicle-history-app` writes queryable history records.
- `vehicle-analytics-app` updates state and statistics.

### DLQ Record

Topic: `vehicle.dlq.gb32960.v1`

Minimum fields:

- `schemaVersion`
- `stage`
- `errorCode`
- `errorMessage`
- `receiveTime`
- `peer`
- `platformAccount` if known
- `vin` if known
- `rawBytes` when available
- `metadata`

Typical stages:

- `FRAME_DECODE`
- `BODY_PARSE`
- `AUTH`
- `KAFKA_PRODUCE`
- `ACK_WRITE`

## ACK and Failure Semantics

### Success Path

1. Receive TCP bytes.
2. Decode a complete GB32960 frame.
3. Validate checksum/auth/session rules.
4. Build raw and normalized Kafka records.
5. Produce required Kafka records successfully.
6. Send GB32960 success ACK.

### Kafka Produce Failure

If required Kafka production fails, the service must not send a success ACK.

Allowed behavior:

- Retry within a bounded timeout.
- Return a GB32960 failure response when the protocol allows it.
- Close the channel after repeated produce failures.
- Emit local error logs and metrics.

Do not silently ACK frames that have not crossed the Kafka durability boundary.

### History or Analytics Failure

Failures in `vehicle-history-app` or `vehicle-analytics-app` must not affect GB32960 ACKs. They are handled by Kafka offset retry, DLQ, operational alerting, and backfill/replay.

## Current Coupling to Remove

`bootstrap-all` currently assembles protocol modules, Kafka producer/consumer, archive sink, event file store, history API, vehicle state, vehicle statistics, command gateway, and multiple inbound protocols in one application.

The split should remove these production couplings:

- Protocol runtime directly containing archive and event-file-store writers.
- Protocol runtime directly containing statistics processors.
- History query API sharing the same process as TCP ingest.
- Kafka consumer workers living in the same all-in-one app as protocol listeners.
- Operational scaling tied to one JVM for ingest, storage, queries, and analytics.

## Migration Plan

### Phase 1: Add Split App Entrypoints

Add new app modules:

- `modules/apps/gb32960-ingest-app`
- `modules/apps/vehicle-history-app`
- `modules/apps/vehicle-analytics-app`

Keep `modules/apps/bootstrap-all` unchanged as the fallback runtime.

### Phase 2: GB32960 Ingest App

Create a production profile for `gb32960-ingest-app`:

- Enable `protocol-gb32960`.
- Enable Kafka producer.
- Disable local archive.
- Disable event-file-store.
- Disable history API.
- Disable vehicle state/stat services.
- Disable unrelated protocols.

Acceptance:

- GB32960 TCP port starts.
- Platform login works.
- Realtime report frames are parsed.
- Kafka records are produced with VIN keys.
- ACK is sent only after Kafka success.

### Phase 3: History App

Create `vehicle-history-app`:

- Enable Kafka consumer.
- Enable archive sink.
- Enable event-file-store.
- Enable event-history HTTP API.
- Disable protocol listeners.
- Disable analytics processors.

Acceptance:

- Consumes `vehicle.raw.gb32960.v1`.
- Writes raw `.bin` files.
- Consumes `vehicle.event.gb32960.v1`.
- Writes Parquet/DuckDB indexes.
- Queries can locate raw frames and decoded snapshots.

### Phase 4: Analytics App

Create `vehicle-analytics-app`:

- Enable Kafka consumer.
- Enable vehicle state if a state backend is configured.
- Enable vehicle statistics.
- Disable protocol listeners.
- Disable archive and event-file-store.

Acceptance:

- Consumes `vehicle.event.gb32960.v1`.
- Maintains expected per-VIN state/stat outputs.
- Can be restarted and resume from Kafka offsets.
- Does not affect ingest ACK latency.

### Phase 5: Parallel Run and Cutover

Run old and new paths in parallel for a bounded validation window.

Compare:

- Received frame count.
- Kafka produced record count.
- Raw archive file count.
- Unique VIN count.
- History query count and sample query correctness.
- Analytics output count and sample values.
- GB32960 ACK latency before and after split.

Cut over only after counts and sample queries match within the agreed tolerance.

## Operational Guidance

Scale services independently:

- Scale `gb32960-ingest-app` by connection count and Kafka produce latency.
- Scale `vehicle-history-app` by disk throughput, consumer lag, and query latency.
- Scale `vehicle-analytics-app` by consumer lag and computation latency.

Monitor:

- Kafka producer error rate and p99 produce latency.
- ACK p99 latency.
- TCP active connections.
- Decode failures and DLQ counts.
- Consumer lag per group.
- Archive write latency and failure rate.
- Event-file-store flush latency and failure rate.
- Analytics processing latency.

## Risks and Mitigations

Risk: Kafka outage blocks GB32960 ACKs.

Mitigation: bounded producer retry, clear failure ACK/close behavior, Kafka cluster monitoring, and optional local emergency spool only as a later explicit design.

Risk: Two Kafka topics for raw and normalized events can diverge.

Mitigation: include `eventId` and `sourceRawEventId`, produce records transactionally if required, or publish a single envelope containing both raw and normalized sections in the first implementation if transaction support is not ready.

Risk: History app reprocessing duplicates archive/index records.

Mitigation: deterministic archive keys and idempotent index writes keyed by `eventId` or `rawArchiveUri`.

Risk: Analytics logic changes require backfill.

Mitigation: keep raw and normalized topics with sufficient retention; allow analytics consumers to reset offsets or run a backfill group.

Risk: Query APIs accidentally depend on ingest internals.

Mitigation: history APIs should depend on `event-file-store`, `sink-archive`, and decode libraries only, not `gb32960-ingest-app`.

## Open Decisions

These are intentionally left for implementation planning:

- Whether Kafka production uses two independent sends or a transaction for raw + normalized records.
- Exact protobuf/JSON schema shape for `vehicle.raw.gb32960.v1` and `vehicle.event.gb32960.v1`.
- Whether latest snapshot state belongs only to `vehicle-analytics-app` or is also materialized by `vehicle-history-app` for query convenience.
- Initial Kafka partition count and retention duration.
- Whether command downlink stays with `command-gateway` or gets a separate command service later.

## Completion Criteria

The split is complete when:

- `gb32960-ingest-app`, `vehicle-history-app`, and `vehicle-analytics-app` are separate runnable app modules.
- `gb32960-ingest-app` can receive live GB32960 data and ACK after Kafka success.
- `vehicle-history-app` can consume Kafka and provide raw/history/snapshot query APIs.
- `vehicle-analytics-app` can consume Kafka and compute state/stat outputs.
- `bootstrap-all` remains available for development and rollback.
- Verification covers build, app startup, Kafka production/consumption, raw archive writes, history queries, analytics outputs, and ACK latency.
