Obligations register
Every testable promise our legal text makes, what keeps it, and whether it holds today. The terms are the spec: product code must satisfy these rows, and product changes that touch personal data are checked against them. A gap row is not yet kept. If a gap row's wording isn't published yet, the wording waits on a draft branch until the code ships.
Statuses: met: the code keeps the promise, checked against the current code. partial: kept in part; the finding below says what's missing. gap: not kept yet. process: kept by people, not code.
Code references: P = aextra-vibekit/cloudflare/proxy/src, A = aextra-vibekit/apps/admin/src, R = aextra-remail/apps/admin/src, VK = aextra-vibekit repo root, RM = aextra-remail repo root. Line numbers drift, so function names are given where they exist. Test commands under "How to verify" run in aextra-vibekit/cloudflare/proxy unless another folder is named.
Sources: vibekit/terms §9.2 means the VibeKit Terms of Service, clause 9.2. "(draft)" means the wording is on the draft/remail-unsubscribe branch and isn't published yet.
Last verified: 25 September 2026, against aextra-vibekit 8f8914f and aextra-remail b264ac9.
VibeKit
| ID | Obligation | Source | Product | Code / process | Status | How to verify |
|---|---|---|---|---|---|---|
| V-01 | Users accept the current Terms and Privacy Policy before using the admin, and each acceptance is recorded (user, version, time, country, user agent) | vibekit/terms §1; vibekit/privacy §2.1 | VibeKit | A/components/TermsGate.tsx (mounted in A/components/ProtectedRoute.tsx); P/routes/api.ts POST /api/account/terms (409 unless the version is current); P/lib/account.ts recordTermsAcceptance; D1 terms_acceptances (VK/cloudflare/proxy/migrations/0011_terms_acceptances.sql); VK/packages/prompts/src/legal.ts TERMS_VERSION |
met | Sign in with a new account, accept, then SELECT * FROM terms_acceptances in D1 |
| V-02 | Connected credentials are encrypted at rest (AES-256-GCM) and never returned to the browser | vibekit/privacy §2.1; vibekit/privacy §9 | VibeKit | P/lib/crypto.ts (AES-GCM envelope); P/lib/provision.ts; P/lib/auth-config-ops.ts redactCredentials |
partial | GET /api/apps/:id with the owner's token must return no secretEnc or apiKeyEnc fields |
| V-03 | End users' Google sign-in requests only the openid and email scopes. Customers' admin Google sign-in (Supabase) receives email, verification, name and profile-picture URL; the name is used only for product-update and onboarding emails, and the picture is not used |
vibekit/privacy §2.1, §2.3, §4; remail/privacy §2.1, §2.3, §4 | VibeKit, ReMail | End users: P/routes/auth.ts (scope=openid email). Customers (admin, shared with ReMail): A/lib/adminAuth.ts signInWithGoogle → Supabase Google provider (default scopes); P/lib/customer-sync.ts copies full_name into the Name field of our customer list, which the ReMail lifecycle emails use (the Resend onboarding-signup hook was removed, aextra-vibekit #103). Nothing reads avatar_url/picture (search A/ and R/ for avatar_url and picture) |
met | Start an end-user Google sign-in and read scope=openid email in the accounts.google.com URL; start an admin Google sign-in and confirm the scopes are no wider than email profile (plus openid); grep shows no use of the picture |
| V-04 | No password sign-in for production accounts | vibekit/privacy §2.1 | VibeKit | A/lib/adminAuth.ts (password path only when import.meta.env.DEV); A/components/SignIn.tsx; Supabase Auth settings |
met | In apps/admin, npm run build then grep -c devSignInWithPassword dist/assets/*.js gives 0. In the Supabase dashboard, the Email provider has password sign-in disabled |
| V-05 | End users' matched records are encrypted at rest, in the database and in the session working copy | vibekit/privacy §2.2; vibekit/privacy §9 | VibeKit | P/lib/session.ts (app_users.record_blob); P/lib/session-snapshot.ts; P/durable-objects/session-channel.ts |
met | npm run test:session-snapshot |
| V-06 | Retention: request and alert logs 30 days; sign-in events 90 days; change log 1 year; device records 90 days after last seen; revoked access 90 days; orphan sign-in identities deleted; unused authorization codes 1 day; revoked authorizations 30 days; form uploads 7 days; edge cache 30 days or less; end users' sign-in links and codes 15 minutes; customers' admin sign-in links and codes 1 hour | vibekit/privacy §10 | VibeKit | P/lib/logging.ts (TTLs and sweep); P/lib/retention.ts; hourly cron (VK/cloudflare/proxy/wrangler.toml crons, P/index.ts scheduled); P/routes/data.ts CACHE_MAX_AGE; P/lib/otp.ts; R2 lifecycle on aextra-vibekit-uploads; Supabase magic-link expiry (default 3600 s) and the email text in P/routes/auth-email-hook.ts |
partial | npm run test:retention; npx wrangler r2 bucket lifecycle list aextra-vibekit-uploads shows a 7-day rule; Supabase Auth → Email OTP expiry is 3600 s or less, and the admin sign-in email says one hour; P/lib/otp.ts CODE_TTL_MS is 15 minutes |
| V-07 | Deleting a project permanently erases its app: sign-ins stop, and configuration, end-user identities and sessions, sign-in history, logs, uploaded files and specifications are deleted | vibekit/privacy §10 | VibeKit | P/lib/app-purge.ts purgeApp (and its late sweep); P/routes/api.ts DELETE /api/apps/:id; A/lib/projects.ts deleteProjectPermanently |
partial | npm run test:app-purge; delete a test project, then look for its appId in D1, KV and R2 |
| V-08 | Self-service account deletion erases every owned app, saved tokens, AI-agent connections and their change log, the customer list entry (with survey answers), Supabase rows and the sign-in | vibekit/privacy §10 | VibeKit | P/lib/account.ts deleteAccount (step customers_base → P/lib/customer-sync.ts removeCustomer); P/routes/api.ts DELETE /api/account; A/components/AccountModal.tsx |
met | npm run test:account; delete a test account, then check D1, Supabase and the VibeKit Customers base (no row with that User ID) |
| V-09 | Emailed deletion requests from the account address are completed within 30 days; customers' requests to delete end-user records are actioned without undue delay | vibekit/privacy §10; vibekit/privacy §11 | VibeKit | Process: someone monitors privacy@aeropage.io and runs the deletion (V-07/V-08 tooling) |
process | Owner confirms who monitors the inbox; keep a log of each request, with the date received and the date done |
| V-10 | Product-update emails: an opt-out tick box at emailed-link sign-up; an on/off switch under My account; opting out or unsubscribing works; opted-out users stay on the list marked unsubscribed and get no onboarding emails or surveys | vibekit/privacy §4 | VibeKit | A/components/SignIn.tsx tick box; P/lib/account.ts marketingOptedOut; P/routes/api.ts /api/account/marketing (also setCustomerOptOut); A/components/AccountModal.tsx; P/lib/customer-sync.ts (Opted out on every row; a ReMail "never email me" / "this project" unsubscribe from the lifecycle project sets marketing_opt_out); ReMail lifecycle templates are Marketing type and every send filters out Opted out |
met | npm run test:customer-sync; sign up with the box ticked and check the row shows Opted out after the next sync; switch it under My account and check the row at once; unsubscribe from a lifecycle email and check My account shows it off after the next sync |
| V-11 | End users and form submitters are never added to our lists; form mailing lists use only the customer's own Resend key | vibekit/privacy §2.2; vibekit/privacy §4 | VibeKit | P/routes/data.ts (form → audience only with hasOwnResendMailer); P/lib/email.ts addResendAudienceContact |
met | Submit a form on an app without its own Resend key: the logs show "resend-audience skipped" |
| V-12 | "Describe view" sends the field names and at most 5 records, each value shortened, to Workers AI; customer data is never used to train models | vibekit/privacy §6 | VibeKit | P/routes/api.ts view-describe route (maxRecords: 5, values cut to 80 characters, prompt capped at 4000); models run on Workers AI |
met | Code review of the view-describe route |
| V-13 | The shared design-reference library, its public screenshots, and the fact that we don't record who submitted each reference are disclosed and true | vibekit/terms §12.3; vibekit/privacy §6 | VibeKit | VK/cloudflare/design-enrich/src/index.ts (stores URL and brand only) |
met | Read the stored library row: it has no user id |
| V-14 | The provider list is accurate, and is updated before a new provider processes end-user data | vibekit/privacy §8; vibekit/privacy §12; vibekit/terms §11 | VibeKit | Process + content/shared/subprocessors.md (kept word for word in step by npm run check) |
process | Before adding any binding or API to VK/cloudflare/proxy/wrangler.toml or a new outbound host, check §8 lists the provider |
| V-15 | Customers are notified of a personal-data breach affecting their data without undue delay, and authorities where required | vibekit/terms §11; vibekit/terms §17; vibekit/privacy §9 | VibeKit | Process: needs an incident runbook (who decides, how customers are contacted, the 72-hour regulator clock) | gap | A written runbook exists and names an owner |
| V-16 | The admin loads no Google Fonts, advertising cookies, cross-site trackers or analytics pixels | vibekit/privacy §2.4 | VibeKit | A/main.tsx (@fontsource-variable/inter); VK/apps/admin/index.html (no third-party tags) |
met | Load the admin with the browser's Network tab open: no requests to fonts.googleapis.com, fonts.gstatic.com or analytics hosts |
| V-17 | Quick Proxy links can't be used as an open redirect | vibekit/terms §13.3 | VibeKit | P/lib/quick-redirect.ts decideRedirect (plus interstitial); P/routes/quick.ts |
met | npm run test:quick-redirect |
| V-18 | Customers are warned about risky configuration: legacy-open and Public queries, secret-looking variables, token breadth, the "log in as" secret | vibekit/terms §9.3 | VibeKit | P/lib/endpoint-warnings.ts; P/lib/secret-heuristic.ts; MCP (P/routes/mcp.ts); admin warnings in A/pages/AppDetail.tsx and A/lab/onboarding/Steps.tsx |
met | npm run test:endpoint-warnings |
| V-19 | Sign-ins stop immediately for a deleted or disabled app, everywhere; the rest of the app stops within about a minute | vibekit/privacy §10 | VibeKit | P/routes/auth.ts getActiveApp (on every sign-in route) → P/lib/app.ts getApp |
partial | Disable an app, then try a sign-in within 60 s from a different region |
| V-20 | At least 30 days' notice (best effort) before discontinuing the whole service; reasonable advance notice before charging for a feature in use | vibekit/terms §21; vibekit/terms §15 | VibeKit | Process: owner | process | Any shutdown or pricing plan includes a dated notice email to all customers |
| V-21 | Processor duties: follow instructions only; confidentiality; security as described; help with data-subject requests; delete on request or termination; show compliance | vibekit/terms §11 | VibeKit | Terms §11 + V-07, V-08, V-09, V-14, V-15 | process | Covered by the rows listed |
| V-22 | Our customer list in Airtable holds only account details, progress records and survey answers of VibeKit customers (never app data, customers' own Airtable ids, tokens or end users); it is updated at least hourly and a row is deleted when the account is deleted | vibekit/privacy §2.1; vibekit/privacy §4; vibekit/privacy §8; vibekit/privacy §10 | VibeKit | P/lib/customer-sync.ts (FIELD, buildCustomers, hourly runCustomerSync, removeCustomer); base "VibeKit Customers" |
met | npm run test:customer-sync; read the Users table's fields against FIELD; delete a test account and check its row is gone |
ReMail
| ID | Obligation | Source | Product | Code / process | Status | How to verify |
|---|---|---|---|---|---|---|
| R-01 | Every live marketing email contains a working per-recipient unsubscribe link, added to the footer if the template has none. It opens a page styled with the sender's brand that offers 3 choices: never email me again (this sender, all projects); remove me from this list (project); don't send me this email again (template). A link the author places in a transactional template works the same way. Sign-in code emails for Screens are exempt | remail/terms §9.3; remail/privacy §1 | ReMail | P/remail/lib/unsubscribe.ts applyCompliance (placeholder, dead href="#" links, or footer link); P/remail/routes/send.ts mints one rmutok per recipient (P/lib/tokens.ts); public page P/remail/routes/unsubscribe.ts mounted at /unsubscribe in P/index.ts (GET never writes); starter footers use {{{unsubscribe_url}}} (R/remail/templates/systems/build.ts) |
met | Send a live test and follow the link: each of the 3 choices is recorded at the right scope |
| R-02 | Every live marketing email carries List-Unsubscribe and List-Unsubscribe-Post headers; one-click unsubscribe means "never email me again from this sender". Transactional emails and Screen sign-in code emails carry none |
remail/terms §9.3 | ReMail | P/remail/lib/unsubscribe.ts unsubscribeHeaders; per-email headers in P/remail/lib/providers/resend.ts; one-click POST /unsubscribe/:tok?oc=1 records scope all (P/remail/routes/unsubscribe.ts), rate-limited per token (RL_REMAIL_UNSUB). Needs a live test: Resend must deliver the headers and DKIM should cover them |
partial | Raw headers of a live test email; POST to the one-click URL records an all-projects suppression |
| R-03 | Suppressed recipients are never sent to. This is enforced by the server at send time (per-email suppressed failure, body unchanged for idempotent retries) and shown in the batch UI |
remail/terms §9.3; remail/privacy §2.2 | ReMail | P/remail/routes/send.ts (suppressedHashes after hashing; suppressed rows return failed/suppressed with no provider call; fails closed); P/remail/lib/supabase.ts; RM/supabase/migrations/20260926000000_remail_suppressions.sql; suppressed class in R/remail/batching/classify.ts |
met | Proxy test: send to a suppressed hash, which returns suppressed with no Resend call. Also npm run selfcheck:batching in aextra-remail/apps/admin |
| R-04 | A sender postal address is a required project setting before live marketing sends; it is added to every marketing email's footer next to the unsubscribe link. Transactional sends don't require it and don't carry it | remail/terms §9.3 | ReMail | Setting in R/remail/ProjectSettings.tsx; gate postal_address_required in P/remail/routes/send.ts (today it refuses live sends of either type; the transactional exemption must be built); footer in P/remail/lib/unsubscribe.ts |
gap | A live marketing send on a project with no address is refused; a live transactional send is not; the footer of a live marketing test shows the address and a transactional one doesn't |
| R-05 | The send log and batch records store only a keyed hash, a masked hint, the subject, status, error and Resend reference, never email bodies | remail/privacy §2.2; remail/privacy §7 | ReMail | P/remail/lib/contact-hash.ts (HMAC of user id + normalised address); RM/supabase/migrations/20260924000000_remail_send_log.sql (no body column); R/remail/sendLog.ts; remail_batches.planned holds {h, hint} only |
met | npm run test:remail-contact-hash; \d remail_send_log in the database |
| R-06 | The Airtable token and Resend key are encrypted at rest (AES-256-GCM) and never returned to the browser | remail/privacy §2.1 | ReMail | P/remail/routes/keys.ts (responses are only {keyId} or {ok}); P/lib/provision.ts; P/lib/crypto.ts; user_keys stores only secret_id |
met | npm run test:remail |
| R-07 | In the customer's Airtable, ReMail changes records only as the customer set up: one batch-log record per batch; Screen form submits (update the opened record or add one, per the Screen's setting); Screen actions; campaign "Update record" steps — and only in the fields named in the published Screen or campaign step. It deletes only simulation (rmsim:) batch-log records, never the customer's other records, and the server enforces this |
remail/terms §12.1 | ReMail | Batch logs: P/remail/routes/airtable.ts /records/create and /records/delete, P/remail/lib/batch-guard.ts. Screens: P/remail/routes/publicScreens.ts submit, P/remail/lib/airtable.ts; field lists in the published snapshot (P/remail/routes/screens.ts publish). Actions: P/remail/lib/screenActions.ts. Campaigns: P/remail/runtime/steps.ts |
gap | Submit a Screen form with an extra field in the request: it is not written. Search every Airtable call in P/remail for DELETE: only the simulation guard. Check access-code bookkeeping writes nothing to Airtable |
| R-08 | Users can delete their saved keys, logos, brands and themes, projects (permanently) and their account themselves | remail/privacy §8 | ReMail | Keys: R/components/KeyPicker.tsx → deleteUserKey. Logos: R/lib/brandAssets.ts deleteBrandLogo (removed or replaced logos deleted on brand save, unsaved uploads on cancel, R/remail/brand/BrandPicker.tsx). Brands: R/lib/themes.ts deleteTheme. Projects: R/pages/ProjectsTrash.tsx deleteProjectPermanently. Account: R/components/AccountModal.tsx → P/lib/account.ts deleteAccount (REMAIL_TABLES, deleteOwnLogos) |
met | Delete each item in the UI, then confirm it's gone from Supabase tables and storage |
| R-09 | Suppressions, the send log and batch records are kept for the life of the sender's account and deleted with it | remail/privacy §8 | ReMail | on delete cascade from auth.users: send log and batches (RM/supabase/migrations/20260924000000_remail_send_log.sql), suppressions (RM/supabase/migrations/20260926000000_remail_suppressions.sql) |
met | Delete a test auth user: its remail_send_log, remail_batches and (later) suppression rows are gone |
| R-10 | Users accept ReMail's Terms and Privacy Policy before use, by ticking a box and clicking accept, and the acceptance is recorded (user, version, time, country, user agent) | remail/terms §1 | ReMail | R/components/TermsGate.tsx in ProtectedRoute; POST /api/account/terms with REMAIL_TERMS_VERSION (remail:<date>, R/lib/legal.ts and VK/packages/prompts/src/legal.ts); stored as app_metadata.remail_terms_version (P/lib/account.ts termsMetadataKeys) |
met | Sign in with a new account: the pop-up can't be dismissed, and a terms_acceptances row with remail: appears |
| R-11 | Product-update emails: an opt-out tick box at ReMail sign-up and an on/off switch under My account | remail/privacy §4 | ReMail | R/components/SignIn.tsx opt-out box → R/lib/attribution.ts signupMetadata (marketing_opt_out); My account switch in R/components/AccountModal.tsx → /api/account/marketing |
met | Sign up with the box ticked: the Resend contact is unsubscribed and gets no onboarding |
| R-12 | The ReMail admin's own interface loads no Google Fonts; brand fonts customers choose for emails are disclosed | remail/privacy §2.4 | ReMail | R/main.tsx imports @fontsource-variable/inter; no Google Fonts import in R/index.css. Customer brand fonts: R/remail/brand/fonts.ts (disclosed) |
met | Load the admin with the Network tab open: no fonts.googleapis.com or fonts.gstatic.com requests outside brand-font previews |
| R-13 | "Contact support" either really sends the message, or isn't offered | remail/privacy §2.1 | ReMail | R/components/ContactDialog.tsx opens a pre-filled mailto:help@aextra.ai in the user's own mail app; nothing pretends to send |
met | Submit the form: a message arrives at the support inbox |
| R-14 | Starter templates contain no real people's photos or third-party brands | remail/terms §13.2 | ReMail | Placeholders we generated: RM/apps/admin/public/remail/static/re-components/ and the design-system folders; starter blocks in R/remail/editor/components/* use reAsset. Only generic social-network icons remain |
met | Search R/remail and RM/apps/admin/public/remail for github.com avatar URLs, steve-, Braun, Herman Miller, App Store and Google Play: nothing is found |
| R-15 | No VibeKit or Aeropage leftovers in the ReMail UI; correct LICENSE | owner decision (LEGAL-REPO-PLAN §0) | ReMail | R/lib/adminAuth.ts uses BRAND; R/lib/brand.ts falls back to ReMail; no owner email default in R/lib/devMode.ts; /admin/design routes and embed.js removed; RM/LICENSE.md is a proprietary Aeropage notice. Remaining "VibeKit" mentions are dev-only screens (FirstRun, coach tours), storage keys, and the shared-sign-in wording in account deletion |
met | Search R for "VibeKit" and "aeropage": nothing is found; LICENSE names the right owner |
| R-16 | No unused ReMail legacy routes on the proxy | owner decision (LEGAL-REPO-PLAN §3) | ReMail | The ReMail LEGACY routes are gone from P/routes/api.ts; VK/cloudflare/proxy/scripts/remail-boundary.selfcheck.mjs passes |
met | npm run test:remail-boundary; searching P/routes/api.ts for LEGACY finds nothing |
| R-17 | Before a live send, a checklist shows the recipient count, says what the emails include for their type (marketing: unsubscribe link and postal address; transactional: neither), and says the send can't be undone | remail/terms §9.5 | ReMail | R/remail/batching/ui/BatchProgressDialog.tsx LiveChecklist; campaign launch R/remail/editor/campaign/CampaignLaunch.tsx |
gap | Open the live-send confirm step and check each item is shown |
| R-18 | Emailed deletion requests (projects, credentials, logos, send history, account) from the account address are completed within 30 days, including logos in storage | remail/privacy §8 | ReMail | Process: someone monitors privacy@aeropage.io. The shared deleteAccount doesn't remove Logos/<uid>/*, so logos are deleted by hand |
process | Keep a log of each request, with the date received and the date done; confirm the Logos/<uid>/ folder is empty afterwards |
| R-19 | Images from Airtable records are cached for at most 1 day | remail/privacy §2.2; remail/privacy §8 | ReMail | P/remail/routes/media.ts (cache-control: public, max-age=86400) |
met | curl -sI an /rmedia/... URL and check max-age=86400 |
| R-20 | ReMail adds no open or click tracking to emails | remail/privacy §2.2 | ReMail | P/remail/lib/providers/resend.ts resendPayload (no tracking options; tracking is a setting on the customer's Resend domain) |
met | Code review of resendPayload |
| R-21 | Server logs contain no email addresses, email content or record data | remail/privacy §7 | ReMail | P/remail/index.ts logs method, route, error name and one stack frame; the unsubscribe route logs only supabase_<status>; no ReMail path reaches the global onError since R-16 |
met | Every console. call under P/remail logs no request data; after R-16, no ReMail path reaches the global onError |
| R-22 | Recipients are never added to our own mailing lists or used for our own purposes | remail/privacy §3 | ReMail | The ReMail plane can't import the platform list helpers: P/lib/email.ts is not on the import allowlist in VK/cloudflare/proxy/scripts/remail-boundary.selfcheck.mjs |
met | npm run test:remail-boundary |
| R-23 | The suppression list stores only the keyed hash of the recipient's address (the same HMAC as the send log), the scope, the project or template it applies to, and the time; never the address itself | remail/privacy §2.2; remail/privacy §7 | ReMail | RM/supabase/migrations/20260926000000_remail_suppressions.sql (no address column; owner-select RLS; writes only with the service role); written by P/remail/lib/supabase.ts addSuppression from the unsubscribe route |
met | \d on the suppressions table in the database shows no address column; the proxy test for the unsubscribe route checks the stored row |
| R-24 | A template's type (marketing or transactional) is resolved on the server from the stored template, never from the send request; a campaign sends an email as transactional only when both its launch snapshot and the stored template are transactional | remail/terms §9.3 | ReMail | P/remail/lib/supabase.ts templateModes; P/remail/routes/send.ts prepareSend, sendCampaignEmail; P/remail/lib/unsubscribe.ts applyTransactional |
gap | Send with a forged mode in the request: it is ignored. Switch a launched campaign's template back to marketing: its emails get marketing treatment |
| R-25 | Screen sign-in code emails are sent through the customer's own Resend key and sender address, only when someone asks for a code; the code is never stored (only its hash, in a sealed challenge) | remail/terms §9.7; remail/privacy §2.2 | ReMail | P/remail/routes/publicScreens.ts POST /access/:slug/code |
gap | Code review of the route; a code email in a test Resend account shows the project's sender |
| R-26 | Screen activity events record the event, project, template, Airtable record id and time, never submitted values; there are still no open-tracking pixels or click redirects in emails | remail/privacy §2.2 | ReMail | Supabase remail_events (RM/supabase/migrations/20260926100000…); writers in P/remail/routes/publicScreens.ts and P/remail/runtime/* |
gap | \d remail_events shows no value column for submissions; submit a test form and read the row |
| R-27 | Screen visitors' IP addresses are stored only as one-way hashes, in sign-in attempt counters kept for up to 24 hours; submitted values pass through to the customer's Airtable and are not stored by us | remail/privacy §2.2, §7, §8 | ReMail | Supabase remail_access_limits (RM/supabase/migrations/20260926120000_remail_access_limits.sql); P/remail/lib/supabase.ts |
gap | No raw IP in the table or logs; rows older than 24 hours are gone (a scheduled cleanup, not only an occasional one) |
| R-28 | AI drafting sends only the author's instructions, project name/description/About, chosen field names and types (with choice-field options), Screen names, and (for updates) the current template content or campaign flow, to Google Gemini via Cloudflare AI Gateway; never record values; not used by us to train models; signed-in users only, rate-limited | remail/privacy §2.5, §6 | ReMail | P/remail/routes/ai.ts; prompt builders R/remail/ai/* (generate.ts, blocks.ts, create.ts); AI Gateway aextra settings (logging) |
gap | Code review of every prompt builder: no record values; confirm the models configured (REMAIL_AI_MODEL_* in VK/cloudflare/proxy/wrangler.toml) are Google's; confirm the AI Gateway aextra log settings |
| R-29 | Project API keys are shown once and stored only as a one-way hash; deleting a key stops it working | remail/terms §10.3; remail/privacy §2.1, §7 | ReMail | R/remail/ApiKeysSection.tsx, R/remail/apiKeys.ts; Supabase remail_api_keys (RM/supabase/migrations/20260926140000_remail_api_keys.sql); P/remail/routes/projectApi.ts |
gap | Create a key: the table holds only a hash; delete it: API calls with it are refused |
| R-30 | Template history, published Screens and their link keys, Screen activity, and API key hashes are deleted when their project is deleted from the trash and when the account is deleted; Screens and link keys also when the Screen is unpublished or deleted | remail/privacy §8 | ReMail | R/lib/projects.ts deleteProjectPermanently; P/lib/account.ts deleteAccount (REMAIL_TABLES); on delete cascade in the new migrations |
gap | Delete a test project and a test account: no rows remain in remail_template_versions, remail_screens, the link-key map, remail_events, remail_api_keys |
Shared
| ID | Obligation | Source | Product | Code / process | Status | How to verify |
|---|---|---|---|---|---|---|
| S-01 | legal@aeropage.io and privacy@aeropage.io are monitored, including after the Aeropage sunset (1 October 2026) |
vibekit/privacy §13; remail/privacy §11; vibekit/terms §27; remail/terms §27 | All | Process: owner | process | Owner sends a test message to each address and confirms it arrives |
| S-02 | The contracting entity's registered name is correct ("Aeropage Limited" or "… LLC") | vibekit/terms §27; remail/terms §27 | All | Counsel review (see aextra-vibekit docs/LEGAL-REVIEW-2026-09.md) |
process | Counsel confirms against the Delaware registration |
| S-03 | The Google OAuth consent screen's privacy-policy link points to the live policy | vibekit/privacy §2.3 | All | Owner: Google Cloud Console → APIs & Services → OAuth consent screen | process | Open the consent screen during Google sign-in and follow its privacy link |
Findings from the 25 September 2026 verification
These are the reasons behind every partial or gap row that the seed list had as met, plus problems found along the way. Where the published text says something the code doesn't do, the fix is in the code (build to comply), unless the owner decides the text should change instead.
- V-03 (resolved 26 September 2026 by changing the text): customers who sign in to the admin (VibeKit or ReMail) with Google go through Supabase's Google provider, which asks Google for
email profile, so we receive the person's name and avatar URL too, andVK/supabase/functions/onboarding-signupsendsfull_nameto Resend with the contact. The owner chose to make privacy §2.1, §2.3 and §4 of both products say so rather than change the sign-in flow. - V-06: customer sign-in links for the admin expire after 1 hour (Supabase's default; the email text in
P/routes/auth-email-hook.tssays so). On 26 September 2026 the owner chose to make VibeKit privacy §10 say so (end users' links and codes stay at 15 minutes). Still open: the 7-day lifecycle on theaextra-vibekit-uploadsR2 bucket exists only as a comment inwrangler.toml. Check it withnpx wrangler r2 bucket lifecycle list aextra-vibekit-uploads. - V-02:
GET /api/apps/:id(P/routes/api.ts) returns the whole appconfig, including the encryptedauth.google.secretEncandauth.mailer.apiKeyEnc. It is ciphertext, not the secret, but it skipsredactCredentials. The "256" in AES-256 depends on the length of theENCRYPTION_KEYsecret, which the code does not check. - V-07: projects trashed before 25 September 2026 still have live apps; no one-off purge script exists yet.
project_teamrows rely on a foreign-key cascade that isn't visible in the repo. - V-10: the sign-up opt-out works only for emailed-link sign-up, which is exactly what privacy §4 says. Google sign-ups can use the My account switch. Lifecycle emails now come only from ReMail, and each carries the standard unsubscribe link and
List-Unsubscribeheaders (R-01/R-02); the Resend list writes and onboarding events were removed (aextra-vibekit #103). The old Resend "Vibekit Users" list still holds contacts added before 30 September 2026, and account deletion still removes them (P/lib/account.tsremoveFromProductUpdates) until that list is deleted. - V-19:
getActiveAppreads through a 5-second in-memory cache and KV. In another region a disabled app can still accept sign-ins for up to about 60 seconds. Privacy §10 says sign-ins stop "immediately". - V-04: the admin build has no password sign-in. Whether the Supabase project itself still accepts passwords is a dashboard setting; the owner should check it.
- V-20: the Terms say we will "try to give" 30 days' notice. The obligation is recorded as that best-effort promise.
- R-07 (resolved 25 September 2026): the proxy now enforces the §12.1 promise itself:
/records/createaccepts one batch-marker record in a saved batch-log table, and/records/deleteonlyrmsim:records it reads back from Airtable. - R-14 (resolved 25 September 2026): the starter-template avatars and photos, which recipients' mail apps loaded from github.com, are replaced with placeholders served from ReMail itself.
- ReMail privacy §2.4: since the 26 September 2026 release it says the application's own fonts are self-hosted (R-12); only customer brand fonts load from Google Fonts.
- Stale reference fixed: the seed list pointed V-14 at
shared/subprocessors.mdin the product repo. It lives here, ascontent/shared/subprocessors.md.