DDR-0034: Session Responsibilities and Always-On Receive Pump (Option A)

Status
Accepted

Context
E27 requires a live TCP connection that, after session establishment, primarily carries:
- unsolicited panel-originated events/notifications,
- keep-alives,
- responses to client-originated requests.

Earlier implementation work blurred responsibilities by having Session drive a multi-step “workflow” (discovery/api_link/hello/authenticate/etc.). That model does not match the intended product semantics:
- Linking is analogous to installer wiring/provisioning and is not part of runtime Session behavior.
- Authenticate is a privilege escalation invoked by the application when an operation requires authorization; it is not required for the base session to function and should not be performed automatically.
- After HELLO, the runtime pattern is: pump inbound traffic continuously, decode messages, surface events, and allow the application to issue requests.

Decision
1) Session responsibilities are limited to:
- Transport lifecycle:
  - connect/disconnect
  - reconnect on failure (including socket recreation)
- Session establishment:
  - perform HELLO to obtain session_id, session_key, session_hmac
  - reset per-connection framing state upon (re)connect
- Framed traffic handling:
  - run an always-on receive pump that reads from the socket, deframes, decrypts, parses JSON, and delivers decoded messages to the application via callbacks/handlers
- Keepalive:
  - handle keepalive policy required by the panel (send/receive) and surface failures as disconnect/reconnect triggers

2) Session MUST NOT:
- perform provisioning or linking (api_link)
- decide to authenticate
- automatically execute authenticate as part of “connect”
- embed business/workflow logic for protected operations

3) Always-on receive pump strategy (Option A) is selected:
- Session will own a background receive pump (async task or dedicated thread; project implementation chooses the concrete mechanism).
- The pump begins after HELLO succeeds and stops on disconnect/close.
- The pump is the single consumer of socket reads, preventing competing reads from request code.

4) Authenticate is not part of Session behavior:
- Authenticate is a message-level operation (build/encode/decode) and will be invoked explicitly by the application when required (e.g., after receiving a NOAUTH error response).
- Session may provide a “send request” primitive that can be used to send an authenticate request, but Session must not initiate it.

Consequences
- Session becomes smaller, more reliable, and testable as a transport+session-key manager and message pump.
- Application-level logic controls privilege escalation and retries:
  - request -> response with NOAUTH -> application decides to authenticate -> retry request.
- Future architectural work is required outside Session to manage:
  - mapping outgoing requests to responses (correlation),
  - handling unsolicited events vs responses,
  - timeouts and retry policies,
  - optional “auth-required” escalation policies.

Implementation Notes
- Session will expose lifecycle hooks/callbacks (names illustrative):
  - on_connected()
  - on_session_ready(session_id)
  - on_message(obj)
  - on_disconnect(reason)
  - on_reconnect(attempt, session_id)
  - on_error(exc)
- Framing state (DeframeState) is per-connection and must be reset on reconnect.
- Any unit tests that assumed Session drives linking/auth as part of connect are invalid and must be updated to reflect the accepted responsibility split.

Non-Goals
- This DDR does not define the request/response correlation architecture, authorization policy module, or message routing layers beyond Session. Those will be captured in subsequent ADR/DDR documents.
# DDR-0034: Session Responsibilities and Option A Selection

## Status
Accepted

## Context
The initial E27 implementation blurred responsibilities between:
- Provisioning / installer binding (API_LINK “linking”)
- Session establishment (HELLO -> session keys)
- Privilege escalation (AUTHENTICATE)
- Application request/response orchestration

This created a brittle Session that attempted to drive an application workflow (discovery → api_link → hello → authenticate → optional operations), and encouraged “fixing” production code to match stale unit tests.

We want Session to behave like the physical keypad wiring model:
- Linking is like installer wiring (one-time provisioning), not a session concern.
- Authenticate is like a user entering a PIN to do privileged actions (application-level decision), not a session concern.
- Session is the live connection: establish session keys, then pump frames/events reliably.

## Decision
We select **Option A**:

**Session is responsible only for:**
1. Owning the TCP connection lifecycle (connect / close / reconnect hooks).
2. Performing **HELLO** immediately after connect to obtain session keys.
3. Implementing the framed receive pump using the canonical framing API:
   `DeframeState + deframe_feed(state, chunk)`.
4. Providing “send framed encrypted payload” and “receive framed encrypted payload” primitives.
5. Decrypting inbound schema-0 payloads using the session key and surfacing decoded JSON as events.
6. Emitting keep-alive / restart handling primitives (policy lives above Session).

**Session is explicitly NOT responsible for:**
- API_LINK / linking (belongs to provisioning / installer binding flow).
- Deciding whether to AUTHENTICATE.
- Performing AUTHENTICATE automatically.
- Driving application request/response sequences (beyond basic send/recv primitives).
- Adapting production behavior to accommodate out-of-date unit tests.

## Consequences
- The message library continues to provide builders for authenticate and other requests, but higher layers decide when to use them.
- Unit tests must be updated to match production interfaces (not the other way around).
- Session becomes smaller, easier to reason about, and safer to refactor (Phase-2).

## Notes
- The canonical framing interface is: `deframe_feed(state, chunk)` returning `DeframeResult(ok, frame_no_crc, error)`.
- The length field includes CRC per protocol docs, but `frame_no_crc` explicitly excludes CRC because deframing validates CRC before returning `ok`.
