CinvanaAI
← All projects

Library · Conversations & archives / Games & interaction

Discord Activity Privacy Core

Decide what an activity policy permits, then minimize retained records and plan expiry.

Repository: CinvanaAI/discord-activity-privacy-core

Recorded synthetic example. Source with a complete offline example.

See the idea in action.

Two rejected decisions, one minimized record with no plaintext, a dry-run backfill plan, and one expired record ID.

Example input
One synthetic Discord message event and three policy states.
Captured result
{
  "input": {
    "eventId": "event-1",
    "guildId": "synthetic-guild",
    "actorId": "synthetic-actor",
    "channelId": "synthetic-channel",
    "kind": "message_created",
    "occurredAt": "2026-01-02T03:04:05Z",
    "content": "This plaintext is deliberately not retained.",
    "attachmentCount": 1
  },
  "disabled": {
    "accepted": false,
    "reasons": [
      "archive-disabled",
      "guild-not-allowed",
      "actor-consent-missing",
      "event-kind-not-allowed",
      "pseudonym-key-too-short"
    ]
  },
  "missingConsent": {
    "accepted": false,
    "reasons": [
      "actor-consent-missing"
    ]
  },
  "accepted": {
    "accepted": true,
    "record": {
      "schemaVersion": 1,
      "recordId": "b322de3f49c1771bd1dd76f8",
      "guildRef": "9091e479600ff22e9c46d965",
      "actorRef": "d005ed00a34cb9a806dceb82",
      "channelRef": "648bf417ad9b2ca6a474b30f",
      "kind": "message_created",
      "occurredAt": "2026-01-02T03:04:05.000Z",
      "attachmentCount": 1
    }
  },
  "digestMode": {
    "accepted": true,
    "record": {
      "schemaVersion": 1,
      "recordId": "b322de3f49c1771bd1dd76f8",
      "guildRef": "9091e479600ff22e9c46d965",
      "actorRef": "d005ed00a34cb9a806dceb82",
      "channelRef": "648bf417ad9b2ca6a474b30f",
      "kind": "message_created",
      "occurredAt": "2026-01-02T03:04:05.000Z",
      "attachmentCount": 1,
      "contentLength": 44,
      "contentDigest": "1253cd8fee9dd61b032eb43d0f8c7ed318f4c6889940301a5f80ff359ef1da34"
    }
  },
  "invalidRetentionBackfill": {
    "status": "denied",
    "reasons": [
      "retention-out-of-bounds"
    ],
    "operations": [],
    "estimatedMaximumMessages": 0
  },
  "plaintextRetained": false,
  "backfill": {
    "status": "dry-run",
    "reasons": [
      "execution-not-requested"
    ],
    "operations": [
      {
        "channelRef": "648bf417ad9b2ca6a474b30f",
        "maxMessages": 100
      }
    ],
    "estimatedMaximumMessages": 100
  },
  "expiredRecords": [
    "b322de3f49c1771bd1dd76f8"
  ],
  "discordCalls": 0
}

Try the example.

From the repository root, follow the dependency requirements in the README. This example uses supplied synthetic material.

npm ci
npm run demo

Two rejected decisions, one minimized record with no plaintext, a dry-run backfill plan, and one expired record ID.

Complete setup and instructions ↗

The interesting part.

Collection defaults off, requires both guild/actor consent, pseudonymizes references, and separates backfill planning from execution.

Public continuation

Where it came from.

A June 2025 TypeScript/Discord.js activity experiment.

June 2025 — Historical TypeScript/Discord.js source experiment; not the public continuation date. Source ↗

Reworks the concept around consent, minimized data, private targets and bounded dry-run-first work.

The raw historical implementation and its privacy defaults are not this public package.

Source ↗