Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,22 @@ test/<description> # e.g. test/core-coverage
See [`CURRENT_STATE.md`](./CURRENT_STATE.md) for what has been built, what is
in progress, and what is next.

## Design Decisions

The protocol and trace-format design is documented in
[`docs/adr/`](./docs/adr/) (Architecture Decision Records) and
[`docs/trace-format-spec.md`](./docs/trace-format-spec.md). Key decisions:

- **OCPP 1.6 JSON** is the primary protocol for v0.1. OCPP 2.0.1 is deferred but the architecture supports it (ADR-0001).
- **Two trace formats:** JSON Object (metadata + events array) and JSONL (one event per line). Bare arrays accepted as degenerate (ADR-0002).
- **Canonical `Event` type** with `id`, `messageId`, `timestamp`, `direction`, `messageType`, `action`, `payload`, `rawMessage` (ADR-0003).
- **Direction** is explicit (`CS_TO_CSMS`, `CSMS_TO_CS`, `UNKNOWN`), inferred from action name when missing (ADR-0004).
- **Timestamps** normalized to epoch milliseconds. Missing timestamps are `null`. Out-of-order events are flagged, not silently reordered (ADR-0005).
- **Sessions** derived by correlating `transactionId`, with `connectorId` and `stationId` as secondary groupings (ADR-0006).
- **Malformed traces:** structural errors fail-fast; event-level errors skip-and-flag; size/count limits enforced (ADR-0007).
- **Browser-local processing:** all trace processing client-side. No auto-upload. No telemetry on trace content (ADR-0008).
- **Extensibility:** version-aware, not version-hardcoded. Adding OCPP 2.0.1 is additive (ADR-0009).

## Contributor Guide

See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for setup, conventions, and the
Expand Down
53 changes: 42 additions & 11 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@

## Active Milestone

**M0 — Repository & Tooling Foundation**
**M0.5Protocol & Trace-Format Design Phase**

Setting up the professional monorepo skeleton with CI, linting, testing,
release tooling, GitHub metadata, and agent onboarding docs — before any
feature code lands.
Design spike resolving all foundational design decisions before core
implementation. Produces ADRs, trace format specification, and synthetic
trace fixtures that validate the proposed internal event model.

## What's Done

### GitHub Infrastructure

- ✅ GitHub milestones created (M0, M0.5, v0.1.0, v0.2.0, v0.3.0, v1.0.0)
- ✅ GitHub labels created (type, package, priority, workflow)
- ✅ 11 tracking issues created and assigned to M0 milestone
- ✅ Tracking issues created for M0 and M0.5

### Monorepo & Tooling (PR #12)

- ✅ Root `package.json` with pnpm workspace
- ✅ `pnpm-workspace.yaml` (`packages/*`, `apps/*`)
- ✅ `tsconfig.base.json` (strict TypeScript config)
Expand All @@ -33,38 +35,67 @@ feature code lands.
- ✅ `AGENTS.md` + `CURRENT_STATE.md` (initial versions)

### CI & Release (PR #13)

- ✅ `.github/workflows/ci.yml` — lint, format check, typecheck, test, build on PR + push
- ✅ `.github/workflows/release.yml` — Changesets version PR, npm publish, ecosystem tag + GitHub release
- ✅ `.changeset/config.json` — public access, base branch main

### GitHub Templates (PR #14)

- ✅ `.github/PULL_REQUEST_TEMPLATE.md`
- ✅ `.github/ISSUE_TEMPLATE/bug_report.md`
- ✅ `.github/ISSUE_TEMPLATE/feature_request.md`
- ✅ `.github/ISSUE_TEMPLATE/scenario_request.md`

### Community Docs (in progress — this PR)
### Community Docs (PR #15)

- ✅ `CONTRIBUTING.md` (setup, conventions, PR process, AI-assisted dev section)
- ✅ `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1)
- ✅ `ROADMAP.md` (milestone summary)
- ✅ `README.md` (description, badges, architecture, quickstart, support, links)
- ✅ `SECURITY.md` (vulnerability reporting, security principles)

### Protocol & Trace-Format Design (in progress — this PR)

- ✅ 9 ADRs covering all design decisions:
- ADR-0001: OCPP version scope (1.6 JSON primary)
- ADR-0002: Input trace formats (JSON Object + JSONL)
- ADR-0003: Canonical internal event model
- ADR-0004: Message direction representation
- ADR-0005: Timestamp normalization
- ADR-0006: Session correlation strategy
- ADR-0007: Malformed trace handling
- ADR-0008: Browser-local processing & privacy
- ADR-0009: Future protocol-version extensibility
- ✅ `docs/trace-format-spec.md` — full trace format specification
- ✅ 3 synthetic trace fixtures in `packages/core/src/__fixtures__/`:
- `normal-session.json` — complete charging session (no failures)
- `failed-auth.json` — failed authorization (expects `FAILED_AUTHORIZATION`)
- `connector-fault.json` — connector fault during session (expects `CONNECTOR_FAULT`)
- ✅ `packages/core/src/types.ts` — proposed canonical types (`Event`, `Trace`, `Session`, etc.)
- ✅ `packages/core/src/fixtures/index.ts` — fixture registry
- ✅ `packages/core/src/fixtures.test.ts` — 28 validation tests proving fixtures conform to the proposed event model

## What's Next

1. **M0 complete** → maintainer reviews and merges PRs #12–#15
2. Add required status checks to branch protection (after CI runs on main)
3. Proceed to M0.5 (Protocol & Trace-Format Design Phase)
1. **M0.5 complete** → maintainer reviews and merges this PR
2. Proceed to v0.1.0 (Inspector MVP):
- Issue #13: Core data model + trace parser + event normalizer
- Issue #14: Core timeline + failure detection + summarizer + validator
- Issue #15: Core public API export + package config
- Issue #16: Scenarios package (format + 5 initial scenarios)
- Issue #17: Reporter package (Markdown report generator)
- Issue #18: CLI package (scaffold + inspect + report + scenario commands)

## Known Blockers / Decisions Pending

- None currently.
- None currently. All design decisions resolved in ADRs.

## Package Status Table

| Package | Status | Version |
|---------|--------|---------|
| `@ocpp-debugkit/core` | not started | — |
| `@ocpp-debugkit/core` | in progress (types + fixtures) | 0.0.0 |
| `@ocpp-debugkit/scenarios` | not started | — |
| `@ocpp-debugkit/reporter` | not started | — |
| `@ocpp-debugkit/cli` | not started | — |
Expand Down
34 changes: 34 additions & 0 deletions docs/adr/0001-ocpp-version-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ADR-0001: OCPP Version Scope — 1.6 JSON Primary

## Status

Accepted

## Context

OCPP has multiple versions in production use (1.6, 2.0.1) and two transport
encodings (SOAP and JSON). OCPP 1.6 JSON is the most widely deployed variant
in modern EV charging infrastructure — it is the default for nearly all
new charge point and CSMS implementations. OCPP 2.0.1 adoption is growing but
not yet dominant. SOAP is legacy.

The tool must start with a focused scope. Supporting all versions and transports
from day one would dilute quality and delay the first release. However, the
internal model must not preclude adding 2.0.1 later.

## Decision

**v0.1 supports OCPP 1.6 JSON only.**

- The parser understands OCPP 1.6 JSON message format: `[MessageTypeId, UniqueId, Action, Payload]` for Call, `[MessageTypeId, UniqueId, Payload]` for CallResult, `[MessageTypeId, UniqueId, ErrorCode, ErrorDescription, ErrorDetails]` for CallError.
- The event model and trace format are designed to be version-aware (an `ocppVersion` field exists in trace metadata) so that 2.0.1 support can be added without breaking changes.
- SOAP is not supported and is not planned.
- OCPP 2.0.1 is explicitly out of scope for v0.1 but the architecture does not prevent its addition in a future version.

## Consequences

- v0.1 parser, normalizer, and detection rules are built for OCPP 1.6 message shapes only.
- The trace format includes an `ocppVersion` field (default `"1.6"`) so future traces can declare their version.
- The `Event` type's `action` field uses OCPP 1.6 action names (e.g., `BootNotification`, `Authorize`, `StartTransaction`).
- When OCPP 2.0.1 is added, it will require a new parser variant and potentially new detection rules, but the `Event` model and trace format will remain stable.
- Users with OCPP 2.0.1 traces will receive a clear "unsupported version" message, not silent misinterpretation.
68 changes: 68 additions & 0 deletions docs/adr/0002-input-trace-formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ADR-0002: Input Trace Formats — JSON Object + JSONL

## Status

Accepted

## Context

Users capture OCPP traces from different sources: WebSocket proxies, CSMS
logs, network captures, manual reconstruction. These sources produce data in
different shapes. The parser must accept the most common formats without
requiring users to manually transform their data.

Two formats are prevalent:

1. **JSON Object** — a structured file with metadata and an events array. Suitable for curated trace files, scenario fixtures, and saved debug sessions.
2. **JSONL (JSON Lines)** — one event per line. Suitable for streaming captures, log files, and real-time traces from CSMS logs.

Some users may also paste a single OCPP message or a bare JSON array of messages, but these are secondary cases handled as degenerate forms of the above.

## Decision

**`parseTrace()` accepts two formats: JSON Object and JSONL.**

### JSON Object format

```json
{
"traceId": "string (optional)",
"metadata": {
"stationId": "string (optional)",
"ocppVersion": "1.6",
"source": "string (optional)"
},
"events": [
{
"timestamp": "ISO 8601 string (optional)",
"direction": "CS_TO_CSMS | CSMS_TO_CS",
"message": [2, "unique-id", "Action", { ...payload }]
}
]
}
```

### JSONL format

Each line is a single event object (same shape as elements in the `events` array above). No top-level metadata wrapper. Station ID and OCPP version are inferred from event content (e.g., `BootNotification` payload) or left unknown.

### Detection strategy

The parser detects format by:
1. Attempting JSON parse of the entire input → if it yields an object with an `events` array, treat as JSON Object format.
2. If full JSON parse fails, split by newlines and parse each non-empty line as JSON → JSONL format.
3. If a single JSON array is provided (bare `[[2, "id", "Action", {}], ...]`), treat each element as a raw OCPP message with unknown direction and timestamp.

### Size limits

- Maximum input size: 10 MB.
- Maximum event count: 10,000 events.
- These limits are enforced before parsing begins.

## Consequences

- Users can paste traces from CSMS logs (JSONL) or load curated files (JSON Object) without transformation.
- JSONL traces lack top-level metadata; station ID and version are inferred from message content where possible.
- The parser has a clear detection strategy with no ambiguity.
- Bare OCPP message arrays (no wrapper) are supported as a convenience but lose direction/timestamp info — the UI will show these as "unknown direction" and "unknown time".
- Size and count limits protect against accidental denial-of-service from very large files.
72 changes: 72 additions & 0 deletions docs/adr/0003-canonical-event-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ADR-0003: Canonical Internal Event Model

## Status

Accepted

## Context

OCPP 1.6 JSON messages arrive as bare arrays: `[2, "id", "Action", {payload}]`.
To build timelines, detect failures, and generate reports, the tool needs a
normalized internal representation that:

- Captures all information from the raw message.
- Adds derived metadata (direction, timestamp, message type).
- Is easy to query, filter, and correlate.
- Remains stable as new OCPP versions are supported.

## Decision

**The canonical internal `Event` type is:**

```typescript
interface Event {
/** Generated unique event ID (sequential, stable within a parse). */
id: string;
/** OCPP UniqueId from the message array. */
messageId: string;
/** Normalized timestamp in epoch milliseconds. null if missing. */
timestamp: number | null;
/** Direction of the message. */
direction: Direction;
/** OCPP message type. */
messageType: MessageType;
/** OCPP action name (e.g., "BootNotification"). Present only for Call messages. */
action: string | null;
/** OCPP payload object. */
payload: unknown;
/** Error code, present only for CallError messages. */
errorCode: string | null;
/** Error description, present only for CallError messages. */
errorDescription: string | null;
/** The original raw OCPP message array, unmodified. */
rawMessage: unknown;
}

type Direction = 'CS_TO_CSMS' | 'CSMS_TO_CS' | 'UNKNOWN';

type MessageType = 'Call' | 'CallResult' | 'CallError';
```

### Mapping from raw OCPP message

| OCPP array shape | messageType | action | payload | errorCode |
|---|---|---|---|---|
| `[2, id, action, payload]` | `Call` | `action` | `payload` | `null` |
| `[3, id, payload]` | `CallResult` | `null` | `payload` | `null` |
| `[4, id, errorCode, errorDesc, errorDetails]` | `CallError` | `null` | `errorDetails` | `errorCode` |

### Event ID generation

Event IDs are generated as `evt-<zero-padded-index>` (e.g., `evt-0001`,
`evt-0002`) based on the event's position in the trace. This is stable across
parses of the same trace file and human-readable in debug output.

## Consequences

- Every event has a consistent shape regardless of OCPP message type.
- The `rawMessage` field preserves the original data for the message inspector UI.
- `action` is `null` for CallResult/CallError — the action must be correlated via `messageId` to the originating Call.
- `timestamp` is `null` when the trace entry doesn't include one — the timeline builder handles this (see ADR-0005).
- `payload` is `unknown` at this layer — Zod schema validation happens in `validateMessage()` (v0.1), not in the normalizer.
- The `Event` type is the foundation for `Session`, `TimelineEntry`, and `Failure` types in v0.1.
56 changes: 56 additions & 0 deletions docs/adr/0004-message-direction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ADR-0004: Message Direction Representation

## Status

Accepted

## Context

OCPP 1.6 JSON is a bidirectional protocol over WebSocket. A charging station
(Charge Point / CS) sends messages to the CSMS, and the CSMS sends messages
back. Understanding which side initiated a message is critical for debugging:

- A `BootNotification` sent CS→CSMS is normal.
- A `BootNotification` response sent CSMS→CS is the reply.
- Direction determines whether a message is a request or a response.

However, many trace capture methods (network proxies, log files) may not
explicitly record direction. The tool must handle both cases.

## Decision

**Direction is an explicit field in the trace entry, with an `UNKNOWN` fallback.**

```typescript
type Direction = 'CS_TO_CSMS' | 'CSMS_TO_CS' | 'UNKNOWN';
```

### Trace entry includes direction

Each event in a trace file (JSON Object or JSONL) includes a `direction` field:
```json
{ "direction": "CS_TO_CSMS", "message": [2, "id", "BootNotification", {}] }
```

### Inference when direction is missing

When `direction` is missing or `"UNKNOWN"`, the normalizer infers direction from message type and action:

1. **Call messages (type 2):** Direction is inferred from the action:
- CS→CSMS actions: `BootNotification`, `Authorize`, `StartTransaction`, `StopTransaction`, `Heartbeat`, `StatusNotification`, `MeterValues`, `DataTransfer`, `FirmwareStatusNotification`, `DiagnosticsStatusNotification`.
- CSMS→CS actions: `Reset`, `RemoteStartTransaction`, `RemoteStopTransaction`, `GetConfiguration`, `ChangeConfiguration`, `ChangeAvailability`, `ClearCache`, `UnlockConnector`, `GetLocalListVersion`, `SendLocalList`, `GetDiagnostics`, `UpdateFirmware`, `TriggerMessage`.
- Bidirectional actions (`DataTransfer`): remains `UNKNOWN` if not specified.

2. **CallResult and CallError messages (types 3 and 4):** Direction is the reverse of the originating Call. The normalizer correlates by `messageId` — if the originating Call's direction is known, the response direction is the opposite.

### When inference is not possible

If direction cannot be inferred (e.g., a CallResult without a matching Call, or a `DataTransfer` Call with no direction), the event retains `UNKNOWN` direction. The UI displays this as "Unknown direction" and the detection rules treat it conservatively (no direction-based failure is triggered on `UNKNOWN`).

## Consequences

- Traces with explicit direction are the gold standard — no inference needed.
- Traces without direction (bare message arrays) still work — the tool infers where possible and marks the rest `UNKNOWN`.
- The inference logic is a static mapping of OCPP 1.6 actions, maintained in the normalizer.
- `UNKNOWN` direction does not cause failures but limits detection accuracy — the UI encourages users to provide direction info.
- Bidirectional actions like `DataTransfer` require explicit direction in the trace.
Loading
Loading