| name | compliance-setup |
| description | Scaffold GDPR + CRA compliance features into a Cloudflare Workers + Neon + React/Vite project. Covers consent gate, data export, account deletion/restore, user feedback (star rating), SBOM request/delivery, Termly policy pages, Canny feedback widget, and CI jobs (SBOM generation + i18n audit). |
| user-invocable | true |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
Model routing: Sonnet for implementation; Haiku for verification/scoring; Opus only for explicit architectural decisions.
Scaffold full GDPR + CRA compliance features into this project. Templates live at
~/.claude/skills/compliance-setup/. Work through each step in order.
Step 0 — Resume check
Before doing anything else, check whether .compliance-setup-progress.md exists
in the working directory.
If it exists:
- Read it.
- If
collected_inputs: true is present, extract the stored inputs — do not
re-ask any question whose answer is already recorded.
- Find the first step checkbox that is still
[ ] (unchecked).
- Print:
Resuming from [step name].
- Skip Steps 1–2 entirely and jump directly to the first unchecked step.
If it does not exist: continue to Step 1 as normal.
Step 1 — Gather inputs
Ask the user for all of the following before writing any files:
| Variable | Description | Example |
|---|
PROJECT_NAME | SBOM filename prefix (no spaces) | my-app |
APP_DISPLAY_NAME | App name shown in emails | My App |
NOREPLY_EMAIL | SendGrid from address | noreply@myapp.com |
TERMLY_COOKIE_ID | Termly Cookie Policy dataId | e5b2bd62-... |
TERMLY_PRIVACY_ID | Termly Privacy Policy dataId | 7695fe09-... |
TERMLY_TERMS_ID | Termly Terms of Service dataId | 28c6b006-... |
CANNY_APP_ID | Canny app ID (optional — skip if not using Canny) | abc123 or blank |
CANNY_URL | Canny public board URL (optional) | https://feedback.myapp.com |
R2_SBOM_PREFIX | R2 path prefix for SBOM files | sbom/latest |
After all inputs are collected, write .compliance-setup-progress.md in the
working directory before doing any further work:
# Compliance-Setup Progress
collected_inputs: true
## Inputs
<record each collected input as a key: value line>
## Steps
- [ ] migrations
- [ ] backend-routes
- [ ] frontend-components
- [ ] i18n-keys
- [ ] ci-jobs
- [ ] write-tests
- [ ] verify
Step 2 — Database migrations
Find the next available numeric prefix in src/db/migrations/ (e.g. if the highest is
018_, use 019_, 020_, 021_).
For each migration below, Read the template, then write it to src/db/migrations/ with
the correct prefix. Do not modify the SQL — it is already idempotent.
-
Read ~/.claude/skills/compliance-setup/migrations/001_consent_fields.sql
→ write as src/db/migrations/NNN_consent_fields.sql
-
Read ~/.claude/skills/compliance-setup/migrations/002_user_feedback.sql
→ write as src/db/migrations/NNN_user_feedback.sql
→ ADAPT: if your session/item table is not polls, update the FK reference.
-
Read ~/.claude/skills/compliance-setup/migrations/003_sbom_requests.sql
→ write as src/db/migrations/NNN_sbom_requests.sql
If these columns/tables already exist in src/db/schema.sql, skip the corresponding
migration and note it.
On success, mark - [x] migrations in .compliance-setup-progress.md.
Step 3 — Backend routes
Read each template and write to src/routes/. If the project already has an
src/routes/me.ts, merge the handlers from routes_me.ts into it rather than
creating a separate file.
3a. Me routes (consent, export, delete, restore)
Read ~/.claude/skills/compliance-setup/backend/routes_me.ts and apply:
- ADAPT
buildExportPayload: update the data query to include your project's
primary tables (events, sessions, orders — whatever the user owns). The profile
and consent sections are generic and do not need changes.
- ADAPT
handleDeleteAccount: null out the OAuth ID columns that your project
uses. Remove columns that don't exist in your schema.
- ADAPT cookie clearing: update the cookie name constant if it differs from
COOKIE.SESSION.
3b. Feedback route
Read ~/.claude/skills/compliance-setup/backend/routes_feedback.ts and write to
src/routes/feedback.ts.
- ADAPT: if your session FK column is not
poll_id, rename it in the INSERT
and the upsert conflict clause.
3c. SBOM route
Read ~/.claude/skills/compliance-setup/backend/routes_sbom.ts and write to
src/routes/sbom.ts. No adaptation needed.
3d. SBOM cron / scheduled handler
Read ~/.claude/skills/compliance-setup/backend/sbom_cron.ts.
In src/index.ts (or equivalent scheduled handler), add a call to
processPendingSbomRequests inside the scheduled export. Also add the two helper
functions (generateR2PresignedUrl, sendSbomEmail) or import them from the route file.
Apply substitutions:
PROJECT_NAME → the value from Step 1
R2_SBOM_PREFIX → the value from Step 1
NOREPLY_EMAIL → the value from Step 1
APP_DISPLAY_NAME → the value from Step 1
3e. Register routes
// IRREVERSIBLE: the DELETE /api/me route triggers permanent account deletion.
// Once executed, user data cannot be recovered (recovery window: 30 days via backup).
// Require explicit user acknowledgement in the UI before calling this endpoint.
// Log deletion events with timestamp and hashed user ID for audit trail.
Add these entries to the fetch handler in src/index.ts (adapt to the project's
routing pattern):
POST /api/me/consent → handlePostConsent(request, env, user)
GET /api/me/export → handleExportData(request, env, user, ctx)
DELETE /api/me → handleDeleteAccount(request, env, user, ctx)
POST /api/me/restore → handleRestoreAccount(request, env, user, ctx)
POST /api/feedback → handleSubmitFeedback(request, env, user)
POST /api/sbom/request → handleRequestSbom(request, env, user, ctx)
GET /api/sbom/status → handleGetSbomStatus(request, env, user)
3f. Scheduled cleanup (account deletion)
In the scheduled handler, add a cleanup block that hard-deletes users whose
recovery_backup_expires_at has passed and has_recovery_backup = true:
UPDATE users
SET deleted_at = NULL
WHERE deleted_at IS NOT NULL
AND recovery_backup_expires_at < NOW()
AND has_recovery_backup = true
Also delete the R2 backup object for each expired user before removing the row.
On success, mark - [x] backend-routes in .compliance-setup-progress.md.
Step 4 — Frontend components
Read each template and write to the project's UI directory. Adapt import paths
(e.g. ../contexts/AuthContext, ../api, ../constants/routes) to match the
project's actual structure.
| Template | Target | Substitutions |
|---|
frontend/TermlyEmbed.tsx | ui/src/components/TermlyEmbed.tsx | none |
frontend/CannyFeedback.tsx | ui/src/components/CannyFeedback.tsx | none |
frontend/ConsentPage.tsx | ui/src/pages/ConsentPage.tsx | none |
frontend/CookiePolicyPage.tsx | ui/src/pages/CookiePolicyPage.tsx | TERMLY_COOKIE_ID |
frontend/PrivacyPolicyPage.tsx | ui/src/pages/PrivacyPolicyPage.tsx | TERMLY_PRIVACY_ID |
frontend/TermsPage.tsx | ui/src/pages/TermsPage.tsx | TERMLY_TERMS_ID |
frontend/SettingsPage_cards.tsx | merge exports into ui/src/pages/SettingsPage.tsx | none |
4a. Wire consent gate into App.tsx (or equivalent router)
Inside RequireAuth (or the auth guard), add after the unauthenticated redirect:
if (user.consent_required) return <Navigate to={ROUTES.CONSENT} replace />;
4b. Add public routes
<Route path={ROUTES.COOKIE_POLICY} element={<CookiePolicyPage />} />
<Route path={ROUTES.PRIVACY_POLICY} element={<PrivacyPolicyPage />} />
<Route path={ROUTES.TERMS} element={<TermsPage />} />
<Route path={ROUTES.CONSENT} element={<ConsentPage />} />
4c. Add Canny (if CANNY_APP_ID was provided)
Inside RequireAuth, render <CannyFeedback> only when VITE_CANNY_APP_ID is set:
const AppID = import.meta.env['VITE_CANNY_APP_ID'] as string | undefined;
{AppID && <CannyFeedback user={{ id: user.id, name: user.name, email: user.email }} />}
4d. Footer links
In the Layout footer, add:
<Link to={ROUTES.PRIVACY_POLICY}>{t('login.footer.privacy')}</Link>
<Link to={ROUTES.TERMS}>{t('login.footer.terms')}</Link>
<Link to={ROUTES.COOKIE_POLICY}>{t('login.footer.cookies')}</Link>
{CANNY_URL && (
<a href={CANNY_URL} target="_blank" rel="noopener noreferrer" data-canny-link>
{t('login.footer.feedback')}
</a>
)}
4e. Route constants
Add to ui/src/constants/routes.ts (or equivalent):
CONSENT: '/consent',
COOKIE_POLICY: '/cookie-policy',
PRIVACY_POLICY: '/privacy-policy',
TERMS: '/terms',
4f. Env vars
Add to ui/.env.example (or equivalent):
VITE_CANNY_APP_ID= # optional
VITE_CANNY_FEEDBACK_URL= # optional
Add to wrangler.toml / wrangler.jsonc vars:
TERMS_VERSION = "YYYY-MM"
PRIVACY_VERSION = "YYYY-MM"
R2_ENDPOINT = ""
R2_ACCESS_KEY_ID = ""
R2_SBOM_BUCKET = ""
SENDGRID_API_KEY = ""
Add R2 binding for user backup data:
[[r2_buckets]]
binding = "R2_USER_BACKUPS"
bucket_name = "your-user-backups-bucket"
Also add the consent_required flag to the /api/me response — return
consent_required: user.terms_accepted_at === null alongside the existing user fields.
On success, mark - [x] frontend-components in .compliance-setup-progress.md.
Step 5 — i18n keys
Read ~/.claude/skills/compliance-setup/i18n/en_auth_consent.json and
~/.claude/skills/compliance-setup/i18n/en_settings_compliance.json.
- Find all locale directories under
ui/src/i18n/locales/ (use Glob).
- For each locale:
- Merge the
consent block and login.footer block into auth.json.
- Merge the
privacy, export, sbom, and danger blocks into settings.json.
- Translate each new key into the target language.
- For RTL languages (Arabic
ar, Urdu ur), check the existing file's
encoding style (some store non-ASCII as \uXXXX) and match it.
- Do not overwrite existing keys — only add the missing ones.
On success, mark - [x] i18n-keys in .compliance-setup-progress.md.
Step 6 — CI jobs
Read ~/.claude/skills/compliance-setup/github/workflow_sbom.yml and
~/.claude/skills/compliance-setup/github/workflow_i18n_audit.yml.
Open .github/workflows/ci.yml and merge both jobs in at the end of the jobs: block.
Apply substitutions to the SBOM job:
PROJECT_NAME → the value from Step 1
- Confirm the R2 secret/var names (
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY,
R2_ENDPOINT, R2_BUCKET) match what is configured in the GitHub repo settings.
Adjust if different.
For the i18n-audit job:
- Confirm
npm run i18n:check exists in ui/package.json. If it doesn't, note
this to the user and skip that job — they'll need to add the script first.
On success, mark - [x] ci-jobs in .compliance-setup-progress.md.
Step 6b — Write tests
Write at minimum:
- Consent gate test: call a data endpoint without consent cookie — assert the appropriate gate response.
- Data export test: call
GET /api/me/export — assert the response contains expected user fields.
- Account deletion test: call
DELETE /api/me — assert the account is marked inactive and data is cleared.
On success, mark - [x] write-tests in .compliance-setup-progress.md.
Step 7 — Verify
Failure policy: if npx tsc --noEmit or npm run build fails, stop immediately
and report the full error output. Do not continue. The user must resolve the issue
and re-run (Step 0 will resume from this step).
- Run
npx tsc --noEmit — fix any type errors before proceeding.
- Run
npm run build (or equivalent) and confirm TypeScript compiles cleanly.
- If compliance test files exist (
test/me.test.ts, test/feedback.test.ts,
test/sbom.test.ts), run them.
- Summarise:
- What was created
- What ADAPT comments remain and need manual attention
- What env vars / secrets need to be set before the feature is live
- Whether the i18n:check script is missing (action required)
On success, mark - [x] verify in .compliance-setup-progress.md.
Operational Readiness
SLI examples:
- Consent endpoint success rate: % of POST /api/me/consent calls returning 2xx
- Data export delivery rate: % of export requests where R2 URL is returned within 60s
- Account deletion processing rate: % of scheduled cleanup jobs completing without error
Key failure modes:
- R2 presigned URL generation fails → export hangs; detected by export request timeout rate; mitigation: check R2 bucket permissions and CORS config
- SBOM generation cron fails → requests never fulfilled; detected by sbom_requests rows stuck in pending state > 24h; mitigation: check cron binding in wrangler.toml
- Consent flag not persisted → users re-prompted on every session; detected by repeated consent events per user; mitigation: verify DB write in handlePostConsent
Rollback classification: Mixed — consent/export/feedback routes are reversible. Account deletion (Step 3f) is irreversible once executed — deleted user data cannot be recovered. See IRREVERSIBLE note at Step 3e.