ADR-0104: Session Singularity Is an Ownership Constraint, Not a Singleton Pattern

Status
  Proposed

Context
  The E27 panel enforces a single active socket session per "API device" and will replace an existing
  session if a new connection/client-hello arrives for the same API device. This means the system
  has a “single active session” property at runtime.

  In our library architecture (DDR-0034), Session is intentionally responsibility-limited to:
    - Transport lifecycle (connect/disconnect/reconnect mechanics)
    - Performing HELLO on connect (session bootstrap)
    - Framing receive pump (deframe/validate/resync)
    - Dispatch of inbound messages/events

  Linking and authentication are explicitly out of Session scope.

Problem
  The project needs to model “only one active session at a time” without reintroducing hidden global
  state, test coupling, or implicit lifetimes. A classic singleton pattern (global instance) enforces
  object uniqueness rather than protocol validity, and it obscures the explicit replacement semantics
  required by the E27 protocol.

Decision
  Session MUST NOT be implemented as a process-wide singleton.

  Instead, we enforce “single active session” as an ownership and lifecycle invariant:
    - A higher-level owner (e.g., Client / ConnectionManager / HA integration) owns exactly one
      authoritative Session at a time.
    - Creating a new Session (or initiating a new connect) MUST explicitly retire (close/cancel)
      the previously-owned Session.
    - The owner is responsible for atomic replacement of the “current Session” reference and for
      ensuring only the current Session is allowed to emit events into the application.

  Session remains a normal instantiable object with explicit lifecycle methods (e.g., connect(),
  close()) and no hidden global state.

Rationale
  - Models the E27 protocol accurately: the panel replaces sessions; therefore replacement should be
    explicit in our code, not hidden behind singleton access.
  - Preserves DDR-0034 boundaries: Session stays transport-scoped and non-orchestrating.
  - Improves testability: tests can freely construct/tear down multiple Session objects without
    global resets; reconnect and race-condition tests can represent “old dying session” and “new
    authoritative session” concurrently.
  - Avoids global state: prevents order-dependent tests, cross-instance interference, and implicit
    shutdown responsibilities.

Consequences
  Positive
    - Clear lifecycle and ownership: “who owns the session” is always explicit.
    - Cleaner reconnect semantics and easier fault injection.
    - No module/global state to reset across unit tests.

  Negative / Tradeoffs
    - Requires a small amount of bookkeeping in the owner (tracking the current Session and retiring
      the previous one).
    - Requires explicit rules about event emission (only the current Session is authoritative).

Alternatives Considered
  A1) Implement Session as a process-wide singleton
      Rejected because it encodes global state, obscures replacement semantics, harms test isolation,
      and does not actually guarantee protocol correctness (it guarantees only “one object”, not “one
      active/authoritative connection”).

  A2) Enforce uniqueness by transport resource locks only (without ownership contract)
      Rejected because it hides replacement responsibility and leads to ambiguous event ownership
      during reconnect and failure scenarios.

Implementation Notes (Non-normative)
  - The owner should:
      - hold a single “current_session” reference
      - on reconnect/new connect attempt: close old session, create new session, swap reference
      - ensure only current session dispatches into shared state
  - Session itself should:
      - avoid any global registries or module-level singletons
      - remain fully instantiable for tests and multiple client objects (even if production uses one)

Related Records
  - DDR-0034: Session Responsibility Boundaries (authoritative for Session scope)
  - ADR-0011: Dual Sequence Numbers in E27 Protocol (envelope vs JSON)
  - E27 API Comms Protocol documentation: single socket session per API device; replacement semantics
