← back to gallery

Policy Diff

Catch silent acceptable-use policy changes across every major AI lab.

aiai-policydiffmonitoringcomplianceopenaianthropicgoogle
Open product ↗

policy-diff

Live diff tracker for the acceptable-use policies, usage policies and model specs of every major AI lab — pulled on cron, diffed against the previous snapshot, classified by Claude Haiku, surfaced as a single dashboard so compliance, legal and product teams catch silent wording changes before they break things.

What it does

  1. Crawls 16 policy/spec/terms documents across 10 labs (OpenAI, Anthropic, Google, Meta, xAI, Mistral, DeepSeek, Cohere, Microsoft, Perplexity) every 12 hours.
  2. Snapshots the extracted plaintext into SQLite (WAL) — deduping by SHA-256, gzip-storing the raw HTML for forensic replay.
  3. Diffs each new snapshot against the previous version using a unified-diff Myers algorithm (npm diff).
  4. Classifies each diff via OpenRouter anthropic/claude-haiku-4.5 into one of {added_restriction, removed_restriction, clarified_existing, definition_change, enforcement_change, cosmetic, unknown} with a severity (high/medium/low/noise) and a single-sentence neutral summary.
  5. Serves a public dark-theme SPA at /policy-diff/ with filters by lab / severity / window / full-text, lab roll-up, source detail timelines, per-change diff view, shareable SVG OG cards, and a stats dashboard.

There are zero mocks. Every snapshot is a real HTTP fetch. If a source fails, the dashboard shows it as error under /api/sources and skips — the next cron cycle retries.

Tech stack

Endpoints (all public, no auth)

| Method | Path | Purpose |
|---|---|---|
| GET | /policy-diff/ | SPA shell |
| GET | /policy-diff/health | {status:"ok", uptime_seconds, version} |
| GET | /policy-diff/api/sources | All tracked sources w/ snapshot + change counts |
| GET | /policy-diff/api/source/:slug | Source detail: snapshots, changes, latest text |
| GET | /policy-diff/api/changes | Paginated change feed. Filters: lab, severity, days, q, cursor, limit |
| GET | /policy-diff/api/change/:id | Full change record + classifier output + diff |
| GET | /policy-diff/api/snapshot/:id/text | Raw extracted plaintext |
| GET | /policy-diff/api/snapshot/:id/raw | Raw upstream HTML (gzip-encoded if client accepts) |
| GET | /policy-diff/api/stats | Aggregates: counts, top-labs, severity breakdown |
| POST | /policy-diff/api/refresh | Queue a refresh (1 req/IP/60s). Body: {slug?} |
| GET | /policy-diff/api/refresh/status | Queue depth + recent refresh log |
| POST | /policy-diff/api/source/:slug/notify | Subscribe an email (stub; no SMTP send v1) |
| GET | /policy-diff/api/change/:id/og.svg | 1200×630 shareable SVG card |
| GET | /policy-diff/api/change/:id/og.png | PNG card (requires node-canvas + ENABLE_PNG_OG=true) |

Data sources & refresh cadence

Every source is fetched from a public URL with no auth. Cadence: full sweep every REFRESH_INTERVAL_HOURS (default 12), plus on-demand via POST /api/refresh.

| Slug | Lab | Document | URL |
|---|---|---|---|
| openai-usage-policies | OpenAI | Usage Policies | https://openai.com/policies/usage-policies/ |
| openai-model-spec | OpenAI | Model Spec | https://model-spec.openai.com/2025-09-12.html |
| openai-service-terms | OpenAI | Service Terms | https://openai.com/policies/service-terms/ |
| anthropic-usage-policy | Anthropic | Usage Policy | https://www.anthropic.com/legal/aup |
| anthropic-commercial-tos | Anthropic | Commercial Terms | https://www.anthropic.com/legal/commercial-terms |
| anthropic-consumer-tos | Anthropic | Consumer Terms | https://www.anthropic.com/legal/consumer-terms |
| google-generative-ai-use-policy | Google | GenAI Prohibited Use | https://policies.google.com/terms/generative-ai/use-policy |
| google-gemini-terms | Google | Gemini Additional Terms | https://policies.google.com/terms/generative-ai |
| meta-llama-use-policy | Meta | Llama Acceptable Use | https://www.llama.com/llama3/use-policy/ |
| xai-acceptable-use | xAI | Acceptable Use | https://x.ai/legal/acceptable-use-policy |
| mistral-terms | Mistral | Terms of Use | https://mistral.ai/terms/ |
| deepseek-terms | DeepSeek | Terms of Use | https://www.deepseek.com/en/terms_of_use |
| cohere-saas-agreement | Cohere | SaaS Agreement | https://cohere.com/terms-of-use |
| cohere-usage-guidelines | Cohere | Usage Guidelines | https://docs.cohere.com/docs/usage-guidelines |
| microsoft-services-agreement | Microsoft | Services Agreement | https://www.microsoft.com/en-us/servicesagreement/ |
| perplexity-terms | Perplexity | Terms of Service | https://www.perplexity.ai/hub/legal/terms-of-service |

Run locally

cp .env.example .env       # then set OPENROUTER_API_KEY for classification
npm install
npm start                  # listens on :4822
open http://localhost:4822/policy-diff/

On boot, the cold-start cron enqueues every source after 5 seconds; you should see the snapshot count climb to 16 within a minute. Diffs only show up from the second snapshot of any source onward — so the change feed will be empty until the second refresh cycle.

If OPENROUTER_API_KEY is unset, snapshots and diffs are still recorded; the change feed simply shows unclassified cards until a key is configured.

Database

data/policy-diff.db — better-sqlite3 in WAL mode. Tables: source, snapshot, change, refresh_log, notify_subscription, meta. See db.js for the schema. The raw HTML of every snapshot is gzip-stored as a BLOB so you can replay the original page from /api/snapshot/:id/raw any time.

Deployment

This product ships a DEPLOY_MANIFEST.json and is picked up by the RNDLAB orchestrator (which rsyncs to a Mac mini, installs a systemd unit, configures nginx, posts to the showcase API, and takes a Playwright thumbnail). Secrets are injected from the RNDLAB key vault at deploy time — __INJECT_FROM_VAULT__ placeholders in .env.example get replaced.

License

MIT.