# License Server — Remaining Work (Phase B + C) Mapped to PRD

**Project:** `apnar-business-licence-platform`
**PRD:** `only-for-dev/Product Requirements Document.md`
**Implementation Plan:** `only-for-dev/Implementation Plan.md`
**Status:** Phase A (Production Safety) ✅ Complete — Phase B/C Pending
**Last Updated:** 2025-08-10

---

## Quick Reference: PRD Section → Implementation Plan Phase → Tasks

| PRD Section | Plan Phase | Feature Area | Status |
|-------------|------------|--------------|--------|
| §11, §33, §37 | B1 | Entitlements (snapshot, per-license overrides) | ❌ Not Started |
| §33 | B2 | Plans entity (plans table, CRUD, license.plan_id) | ❌ Not Started |
| §33 | B3 | Product versions (deprecation, min-version enforcement) | ❌ Not Started |
| §43 | B4 | License state machine (statuses, transitions, terminal states) | ❌ Not Started |
| §34 | B5 | Validation logs (90-180 day retention, prune command) | ❌ Not Started |
| §27, §33 | B6 | Global audit log (immutable, all admin mutations) | ❌ Not Started |
| §10, §18, §19 | B7 | Client deactivate / heartbeat / entitlements / environments | ❌ Not Started |
| §22, §33 | B8 | API clients / keys (key_hash, scopes, IP allow-list, auth middleware) | ❌ Not Started |
| §31 | C1 | Validation cache (Redis) + status sweepers | ❌ Not Started |
| §28 | C2 | Observability (JSON logs, request-ID, metrics, failed-webhook alerts) | ❌ Not Started |
| §20, §39 | C3 | Offline tokens + SDK foundation (PHP/Laravel) | ❌ Not Started |
| §26, §51 | C4 | Abuse detection (suspicious domains, IP banning) | ❌ Not Started |
| §49 | C5 | Reporting + retention/backup ops (Filament reports, DR) | ❌ Not Started |
| §48 | C6 | Multi-tenant readiness (organization_id scopes) | ❌ Not Started |

---

## Detailed Task Breakdown (Ready for Future Sprints)

---

### B1 — Entitlements (Flagship) — PRD §11, §33, §37

**Migrations needed:**
```sql
entitlements (id, product_id, code, type: boolean|number|string, description, unique(product_id,code))
plan_entitlements (id, plan_id, entitlement_id, value, unique(plan_id,entitlement_id))
license_entitlements (id, license_id, entitlement_id, value, unique(license_id,entitlement_id))
```

**Services:**
- `EntitlementService` — snapshot plan → license on create / plan change; per-license overrides
- `License::entitlement(code)`, `License::allEntitlements()`
- Signed document includes `entitlements` map

**Admin:**
- `EntitlementResource` (product-scoped)
- Plan relation-manager for entitlements
- License relation-manager for snapshot overrides

**API:**
- Admin entitlement CRUD
- `GET /api/v1/license/entitlements` (signed)

**Tests:** Snapshot on create; later plan change doesn't mutate old license; override honored; signature carries entitlements

**PRD Links:** §11 (per-license entitlement snapshot), §33 (schema), §37 (SaaS entitlements in signed doc)

---

### B2 — Plans Entity — PRD §33

**Migration:**
```sql
plans (id, product_id, code, name, interval: monthly|quarterly|annual|lifetime|trial, max_activations, max_domains, is_active, unique(product_id,code))
```
- Add `licenses.plan_id` FK (nullable), keep `licenses.plan` string for display

**Admin:** `PlanResource` (CRUD)
**API:** Plans CRUD
**Tests:** Plan CRUD; license created against plan gets entitlement snapshot

---

### B3 — Product Versions — PRD §33

**Migration:**
```sql
product_versions (id, product_id, version, status: active|deprecated|blocked, released_at, unique(product_id,version))
```

**Admin + API:** CRUD
**Optional enforcement:** Per-product `enforce_min_version` → validation rejects older versions with `version_unsupported`

**PRD Links:** §33 (schema), deprecated/blocked surfaced in signed doc

---

### B4 — License State Machine Completion — PRD §43

**Migrations:** Widen `licenses.status` to:
```
pending | active | grace | expired | suspended | revoked | cancelled
```
Add: `grace_ends_at`, `suspended_at`, `revoked_at`, `cancelled_at`
Denormalized indexed: `effective_status`, `effective_expires_at` (solves S2 dashboard query)

**Services:**
- `LicenseStatus` enum
- `LicenseStateMachine` service enforcing §43 rules:
  - Revoked is terminal (Rule 3)
  - Expired cannot create production activations (Rule 2)
  - Grace policy configurable (Rule 1)

**Admin/API:** Add `suspend`, `cancel` transitions + reinstate; legacy `blocked` → suspended semantics

**Tests:** Each transition pre/post state; revoked cannot reactivate; cancelled returns blocked wire status

---

### B5 — Validation Logging + Retention — PRD §34

**Migration:**
```sql
validation_logs (id, license_id nullable, key_hash indexed, status, environment, ip, domain, app_version, instance_id, response_code, created_at indexed)
```

**Changes:**
- `validate()` writes `validation_logs` (sampleable), NOT `license_events` for every call
- `license_events` reserved for meaningful lifecycle events

**Command:** `license:prune-logs {--days=180}` scheduled daily
**Admin/API:** Validation log reader + "recent failures" dashboard

---

### B6 — Global Audit Log — PRD §27, §33

**Migration:**
```sql
audit_logs (id, actor_type, actor_id, action, subject_type, subject_id, reason, ip, user_agent, metadata json, created_at)
```

**Service:** `AuditService` recording every admin state change (Filament actions, admin API, CLI commands)
**Constraint:** No update/delete endpoints (not silently editable, §27)

**Tests:** Each transition writes an audit row

---

### B7 — Client Deactivate / Heartbeat / Environments — PRD §10, §18, §19

**Endpoints:**
- `POST /api/v1/license/deactivate` — set domain inactive, free activation slot (§18); idempotent for unregistered domains
- `POST /api/v1/license/heartbeat` — update `last_seen_at`/`app_version`/`instance_id`; short response
- `GET /api/v1/license/entitlements` (from B1)

**Schema:** Add `environment` column on `license_domains`; per-plan caps (production=x, staging=x, development=unlimited)

**Sweeper:** `check_interval_hours` enforcement — flags stale registrations when `last_seen_at > now - interval`; soft flag only, no auto-revoke

---

### B8 — API Clients / API Keys — PRD §22, §33

**Migrations:**
```sql
api_clients (id, name, description, is_active, created_at)
api_keys (id, client_id, key_hash unique, scopes json, expires_at, revoked_at, ip_restriction json, last_used_at)
```

**Admin + API:** Manage clients/keys, rotation, IP allow-list
**Auth Middleware:** Service-to-service auth (SDK foundation) — validates `X-Api-Key` + request signature + timestamp + replay protection
**Replaces:** Current `auth:sanctum` on internal admin routes

---

### C1 — Validation Cache + Sweepers — PRD §31

- Redis cache of signed docs keyed `license:{key_hash}:v{valid_until}` (short TTL)
- State change bumps `license:{id}:rev` — revocation busts cache instantly
- Command: `license:sweep-status` (every 15 min) flips indexed status between active/grace/expired using `valid_until`/`grace_ends_at`
- Dashboard: aggregate counters (MariaDB/Redis) instead of live rescan

---

### C2 — Observability — PRD §28

- JSON structured logs
- Request-ID middleware
- Response-time + status metrics
- Failed-webhook alerts (email + dashboard pending count)

---

### C3 — Offline Tokens + SDK Foundation — PRD §20, §39

- Signed payload gains `issued_at` + `offline_valid_until` window
- PHP/Laravel SDK wrapping activate/validate/heartbeat/deactivate + local cached validation honoring the window

---

### C4 — Abuse Detection — PRD §26, §51

- `suspicious_domains` watchlist (temp-mail domains, punycode lookalikes)
- Repeated-failure counters from `validation_logs`
- IP banning via throttle integration

---

### C5 — Reporting + Retention/Backup Ops — PRD §49

- Filament report pages (product/plan-wise counts, activation trends, revocations, trial conversions) on aggregate tables
- Retention sweeper tuning
- Documented daily backup + restore verification (§35)
- DR RPO≤15m / RTO≤1h (§36)

---

### C6 — Multi-Tenant Readiness — PRD §48

- Nullable `organization_id`/`tenant_id` scopes on `licenses` now, so future org licensing needs no redesign

---

## Schema Inventory (Delta vs Current)

| Table | Current | Addition (Phase B/C) |
|-------|---------|---------------------|
| products | exists | — |
| customers | exists | — |
| licenses | exists + key_hash, key_last4 | + plan_id, new statuses, grace_ends_at, suspended_at, revoked_at, cancelled_at, effective_status, effective_expires_at, organization_id |
| license_domains | exists | + environment |
| license_events | exists | unchanged |
| webhook_outbox | exists | unchanged |
| **plans** | ❌ missing | new (B2) |
| **entitlements** | ❌ missing | new (B1) |
| **plan_entitlements** | ❌ missing | new (B1) |
| **license_entitlements** | ❌ missing | new (B1) |
| **product_versions** | ❌ missing | new (B3) |
| **webhook_events** | exists (A5) | — |
| **idempotency_keys** | exists (A4) | — |
| **validation_logs** | ❌ missing | new (B5) |
| **audit_logs** | ❌ missing | new (B6) |
| **api_clients / api_keys** | ❌ missing | new (B8) |

---

## API Inventory (Final Target)

| Category | Endpoints |
|----------|-----------|
| **Public (client)** | activate, validate, ping, **deactivate, heartbeat, entitlements** — all rate-limited |
| **Platform inbound** | `POST /api/v1/webhooks/platform` (HMAC signed) + versioned |
| **Admin (REST, API key + RBAC)** | dashboard, events, webhooks (in/out), products (+ regenerate-key), **plans, entitlements, versions**, customers, licenses CRUD + block/unblock/**suspend/cancel/revoke**/extend/domains, payments, audit, validation-logs, **api-clients/api-keys** |

---

## Suggested Sprint Order for Future Work

| Sprint | Focus | Deliverables |
|--------|-------|--------------|
| 1 | **B8 + B1 + B2** | api_clients/api_keys auth + Entitlements + Plans (core MVP data model) |
| 2 | **B4 + B7** | State machine + Client deactivate/heartbeat/entitlements |
| 3 | **B5 + B6** | Validation logs + Audit logs |
| 4 | **B3 + C1** | Product versions + Validation cache/sweepers |
| 5 | **C2 + C3** | Observability + Offline tokens + SDK |
| 6 | **C4 + C5 + C6** | Abuse detection + Reporting/backup + Multi-tenant |

---

## How to Resume in Opencode

```bash
# In a future session:
> read only-for-dev/PHASE_B_C_REMAINING_WORK.md
> "Start Sprint 1: implement B8 api_clients/api_keys with auth middleware"
# or pick any specific item:
> "Implement B1 Entitlements per the PRD mapping in this file"
```

---

## Key Files to Create/Modify (Per Sprint)

| Sprint | Files |
|--------|-------|
| B8 | Migration, `ApiClient`, `ApiKey`, `AuthenticateApiClient` middleware, Filament resource, route auth change |
| B1 | 3 migrations, `EntitlementService`, `License` helpers, signed doc update, Filament resources, API endpoints |
| B2 | Migration, `Plan` model, `PlanResource`, API, `licenses.plan_id` FK |
| B4 | Migration, `LicenseStatus` enum, `LicenseStateMachine`, admin transitions |
| B7 | Public controller endpoints, `license_domains.environment`, env caps logic |
| B5 | Migration, `ValidationLog` model, `validate()` logging, prune command, admin reader |
| B6 | Migration, `AuditLog` model, `AuditService`, Filament/API integration |
| C1 | Redis cache keys, `license:sweep-status` command, dashboard aggregates |

---

**Next Up:** When ready, start with **B8 (API Clients/Keys)** — it unblocks secure internal API access and is a prerequisite for proper platform integration per PRD §22.