M MemberIntel KB
Activity Decisions

decision

ADR-0001: Entitlement service shape

ADR-0001 (Accepted, 2026-05-08): Entitlement service shape.

Status: Accepted
Date: 2026-05-08
Deciders: Seth (Lead Architect), Blair (CEO sign-off pending)

Context

Per SPEC §6 and §8.4, MemberIntel must enforce tier-based model routing
server-side as a single source of truth. The cost review (2026-05-08)
identified that “Haiku-only for Free” is an invariant that must not be
defeatable by an admin tool, debug surface, or internal job. The service
must mint a typed model handle so that callers cannot pick the model.

Decision

Single module at src/memberintel/entitlement/. Public function
check_and_consume(tier, operation) -> ModelHandle. The handle is an
opaque dataclass that carries the model id internally; callers cannot
read .model_id (CI guard rule enforces). The handle’s bound operation
must match the operation arg in llm.call or the call raises
HandleOperationMismatch.

At kickoff: in-process state only. monthly_cap is declared in the
TIERS table but check_and_consume does not decrement any counter.
Slice 1 swaps in a Redis hot path + Postgres usage_counters durable
table behind the same function signature — no caller change.

V1.5 trial-state fields (trial_started_at, trial_ends_at,
card_required_at) are reserved in the eventual usage_counters schema
so the V1.5 trial flow is additive, not a rewrite.

Consequences

Positive:

  • Single source of truth for tier-routing decisions.
  • Opaque handle prevents the “string-typed model” hole the cost review identified.
  • V1.5 trial state slot reserved early.

Negative / costs:

  • Slightly more friction for debugging (can’t print(handle.model_id) in app code).
  • Tests of evals must read .model_id; the eval suite is the trusted boundary documenting that.

Mitigations:

  • __repr__ deliberately omits _model_id so log lines don’t leak it.
  • The eval suite exercises the routing matrix explicitly.

Alternatives considered

  • String-typed model passed through layers — rejected because the cost review flagged this as the exact hole that lets Free leak to Sonnet.
  • Skip the handle, pass tier+operation everywhere — rejected because the tier-to-model mapping is then duplicated at every call site.
For: S Seth Shoultes A AI Engineer B Blair Williams S Santiago Perez Asis P Product Lead