# Consent Management

How LOMA-PORTAL captures, stores, and honors consent (PDPA §19 / GDPR Art. 7).

## When consent is required

| Feature                                  | Required?           | Why                                     |
| ---------------------------------------- | ------------------- | --------------------------------------- |
| Account creation (employee portal)       | No — Contract basis | Employment contract is the lawful basis |
| Marketing emails                         | Yes                 | Not necessary for the contract          |
| Push notifications                       | Yes                 | Granular by category                    |
| Optional integrations (e.g. LINE notify) | Yes                 | Beyond the portal's purpose             |
| Cookies (analytics, marketing)           | Yes                 | EU/UK + Thai PDPC guidance              |

## Data model (added when first consent surface lands)

```prisma
model Consent {
  id            String   @id @default(cuid())
  userId        String   @map("user_id")
  user          User     @relation(fields: [userId], references: [id])
  purpose       String   // 'marketing.email', 'push.notifications', 'cookies.analytics', …
  version       Int      // bumps when the purpose statement changes
  granted       Boolean
  grantedAt     DateTime @default(now()) @map("granted_at")
  withdrawnAt   DateTime? @map("withdrawn_at")
  ipAddress     String?   @map("ip_address")
  userAgent     String?   @map("user_agent")
  @@unique([userId, purpose, version])
  @@map("consents")
}

model ConsentArtifact {
  id        String @id @default(cuid())
  purpose   String
  version   Int
  bodyHtml  String @map("body_html")
  bodyText  String @map("body_text")
  effectiveFrom DateTime @map("effective_from")
  @@unique([purpose, version])
  @@map("consent_artifacts")
}
```

`ConsentArtifact` snapshots the exact text the subject agreed to — required for proof.

## Capture flow

1. Show the artifact (latest version for the purpose).
2. User explicitly opts in/out via a tri-state (granted / withdrawn / pending).
3. On grant: `INSERT Consent { granted: true, grantedAt: now() }`.
4. On withdraw: `UPDATE Consent SET withdrawnAt = now()`.
5. Emit `audit.consent.granted` / `audit.consent.withdrawn`.

## Honoring consent

- Marketing email worker checks `Consent.granted = true AND withdrawnAt IS NULL AND purpose='marketing.email'`.
- Push notification worker checks the matching `purpose='push.notifications'`.
- Analytics SDK gated on cookies consent.

## Withdrawal

Available in the user's account settings + as a single-click unsubscribe link in every marketing email. Withdrawal is recorded immediately and honored within 24h (no batch lag).

## Versioning

If you change the purpose text, bump `Consent.version` + add a new `ConsentArtifact`. Existing users are re-prompted on next login.

## Cross-references

- [`pdpa.md`](pdpa.md) §19
- [`gdpr.md`](gdpr.md) Art. 7
- `.claude/skills/notification-channel/SKILL.md` (consent check in the channel)
