Skip to content

Legal release (Privacy, Terms, changelog)

Critical process. A legal update must never require hunting four places again.

Accuracy first: follow LEGAL_RELEASE_SOP.md (triggers, opsec rule, draft language, founder review, related-surface sweep). This document is the ship checklist after Ryan approves the language.

Use both documents for every Privacy Policy, Terms of Service, or public changelog change.


Before you edit anything

Read LEGAL_RELEASE_SOP.md, then the banner in content/legal/README.md. Do not ship until Ryan has approved the language.

Single source of truth (edit only here in last.vet-web):

Asset Path
Changelog JSON content/legal-changelog.json
Current Privacy Policy LASTVET_PRIVACY_POLICY.md
Current Terms of Service LASTVET_TERMS_OF_SERVICE.md
Prior version archives content/legal/privacy-vX.Y.md, content/legal/terms-vX.Y.md

Never edit by hand:

Copy Why
last.vet-api/src/content/legal-changelog.json Generated by npm run legal:sync-api
Live HTML on last.vet / my.last.vet Deployed from this repo
Privacy notice email body Rendered from API copy of the same JSON

Release checklist (in order)

1. Edit content (this repo only)

  • [ ] Update content/legal-changelog.json (new entry at top per document)
  • [ ] Update LASTVET_PRIVACY_POLICY.md and/or LASTVET_TERMS_OF_SERVICE.md
  • [ ] Version and Effective Date headers in markdown must match the new changelog entry
  • [ ] Archive prior markdown to content/legal/privacy-vX.Y.md / terms-vX.Y.md if superseding
  • [ ] Archive routes under app/(site)/privacy/archive/ and terms/archive/ if new archive pages are needed

2. Validate and sync locally

From last.vet-web:

npm run legal:release

This runs validation and copies legal-changelog.json to ../last.vet-api.

3. Commit and push last.vet-web → main

  • [ ] Push triggers last.vet (Cloudflare Git on last-vet-web)
  • [ ] Push triggers my.last.vet (.github/workflows/deploy-my-last-vet.yml on last-vet-my)
  • [ ] CI runs npm run legal:validate on legal-touched PRs
  • [ ] CI runs npm run legal:smoke on both domains after deploy

4. Commit and push last.vet-api (if JSON changed)

  • [ ] Push so privacy notice emails use the same changelog copy
  • [ ] Redeploy last.vet-api on R940 (ssh -A lastvet-api then /opt/lastvet-gates/scripts/deploy-lastvet-api.sh --prod-only after main is pulled) so privacy notice emails use the new JSON. Do not use the retired ECS script.

5. Verify live (all three public surfaces)

Wait until both Pages projects have finished deploying (last-vet-web via Cloudflare Git, last-vet-my via deploy-my-last-vet.yml). Then:

npm run legal:smoke

Confirm by hand if smoke is not enough:

  • https://last.vet/privacy and /terms (new version header; stale claims gone)
  • https://my.last.vet/privacy and /terms (same new version; this host serves its own copy)
  • https://provider.last.vet footer Privacy/Terms links → https://last.vet/privacy and /terms (canonical; no third copy)
  • https://last.vet/legal/changelog and https://my.last.vet/legal/changelog
  • Prior-version archives still show the old text

Do not send the privacy-notice email until every surface matches. A current last.vet and a stale my.last.vet is a failed release.

6. Send material-change notice (if the changelog entry is material)

After live surfaces match:

  1. Preview: GET /api/internal/privacy-notices/<version>/preview on the prod API (internal Bearer).
  2. Send: POST /api/internal/privacy-notices/<version>/send with includeWaitlist: true for material processor/PHI-location changes (same campaign as v1.1).
  3. In-app: iOS and provider portal link to https://last.vet/privacy / /terms. They do not host a separate policy body. Once last.vet is the new version, those in-app links are the new version. There is no separate inbox-writer for legal notices; the email campaign is the push channel.

What deploys where

Surface Cloudflare / runtime Deploy trigger
https://last.vet Pages project last-vet-web Cloudflare Git build on push to main
https://my.last.vet Pages project last-vet-my GitHub Actions deploy-my-last-vet.yml on push to main
https://provider.last.vet last.vet-provider Links to last.vet legal URLs (no policy copy to deploy)
Privacy notice email last.vet-api on R940 /opt/lastvet-gates/scripts/deploy-lastvet-api.sh after JSON sync

Secrets (Bitwarden + GitHub)

Cloudflare deploy credentials are shared with onset-web (same Last 1 Enterprises account).

Secret Bitwarden SM (lastvet project) GitHub (Last-1-Enterprises/last.vet-web)
CLOUDFLARE_API_TOKEN Yes Yes (Actions)
CLOUDFLARE_ACCOUNT_ID Yes Yes (Actions)

Refresh GitHub secrets from Bitwarden:

last.vet-web/scripts/sync-github-cloudflare-secrets.sh

Manifest: lastvet-gates/bitwarden/secrets-manifest.json


CI workflows

Workflow When Purpose
Validate legal content PR / push touching legal paths Blocks markdown ↔ changelog drift
Deploy my.last.vet Every push to main Builds + deploys last-vet-my, smoke-tests both domains

npm scripts

Command Purpose
npm run legal:validate Fail if markdown headers ≠ changelog JSON (and API copy if sibling repo present)
npm run legal:sync-api Copy JSON to last.vet-api
npm run legal:smoke Live HTTP check: last.vet + my.last.vet versions, provider.last.vet canonical links
npm run legal:release validate + sync-api

Every npm run build and npm run build:my runs validation first.


Optional: auto-sync JSON to API via CI

Add GitHub secret LAST1_REPO_PAT (write access to last.vet-api) to enable future automation. Until then, npm run legal:release + manual API push is required.


Incident: email went out but website looked stale

Usually my.last.vet was not redeployed (separate Pages project). last.vet may already be current. Always run npm run legal:smoke after a legal release.