Aextra Legal
Back to aextra.ai
Legal

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 Google sign-in requests only the openid and email scopes, so Google shares only the email address and whether it is verified vibekit/privacy §2.3; remail/privacy §2.3 VibeKit, ReMail End users: P/routes/auth.ts (scope=openid email). Customers (admin, shared with ReMail): A/lib/adminAuth.ts signInWithGoogle → Supabase Google provider gap Start admin Google sign-in and read scope= in the accounts.google.com URL
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; sign-in links and codes 15 minutes 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 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 900 s or less
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 product-update list entry, Supabase rows and the sign-in vibekit/privacy §10 VibeKit P/lib/account.ts deleteAccount; 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 Resend list
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 vibekit/privacy §4 VibeKit VK/supabase/functions/onboarding-signup (marketing_opt_out); A/components/SignIn.tsx tick box; P/lib/account.ts marketingOptedOut, marketingEmail; P/routes/api.ts /api/account/marketing; A/components/AccountModal.tsx; Resend templates "VK Onboarding · *" met npm run test:account; sign up with the box ticked, then check the Resend contact is unsubscribed and no user.created event fired; open each onboarding template in Resend and check for an unsubscribe link
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

ReMail

ID Obligation Source Product Code / process Status How to verify
R-01 Every live 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) remail/terms §9.3 (draft); remail/privacy §1 (draft) ReMail To build: token in the proxy send path (P/remail/routes/send.ts), public route mounted like /rmedia in P/index.ts, token kind in P/lib/tokens.ts. Templates today use link('Unsubscribe','#') (R/remail/templates/systems/*.ts) gap Send a live test and follow the link: each of the 3 choices is recorded at the right scope
R-02 Every live email carries List-Unsubscribe and List-Unsubscribe-Post headers; one-click unsubscribe means "never email me again from this sender" remail/terms §9.3 (draft) ReMail To build: the contract P/remail/contracts.ts and checkEmail (P/remail/lib/validate.ts) have no headers; the Resend adapter is P/remail/lib/providers/resend.ts. Check that Resend's batch endpoint accepts per-email headers gap 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 (draft); remail/privacy §2.2 (draft) ReMail To build: check after hashing in P/remail/routes/send.ts prepareSend; new suppressions migration in RM/supabase/migrations/; suppressed class in R/remail/batching/classify.ts gap 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; live sends are blocked until it is set, and it is added to every email footer next to the unsubscribe link remail/terms §9.3 (draft) ReMail To build: R/remail/ProjectSettings.tsx, gate in the proxy send path gap Try a live send on a project with no address: it is refused. The footer of a live test shows the address
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 only creates one batch-log record per batch and only deletes simulation (rmsim:) batch-log records. It never changes or deletes the customer's own records, and the server enforces this remail/terms §12.1 ReMail Client: R/remail/batching/marker.ts, R/remail/batching/clearSimulations.ts, R/remail/batching/liveDeps.ts. Server: P/remail/routes/airtable.ts /records/delete and /records/create accept any ids or records partial Proxy test (npm run test:remail-airtable): /remail/airtable/records/delete with a non-rmsim: record id is refused
R-08 Users can delete their saved keys, logos, brands and themes, projects (permanently) and their account themselves remail/privacy §8 (draft) ReMail To build: key delete (proxy DELETE /remail/keys/:keyId exists; R/lib/projects.ts deleteUserKey is unused and misses Resend keys); logo delete (R/lib/brandAssets.ts only uploads); R/lib/themes.ts deleteTheme is unused; project delete is trash only (trashProject); account delete is a mailto (R/components/AccountModal.tsx); shared P/lib/account.ts deleteAccount misses Logos/<uid>/* gap 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 Send log and batches: on delete cascade from auth.users (RM/supabase/migrations/20260924000000_remail_send_log.sql). Suppressions: table not built yet partial 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 (draft) ReMail Today: notice only on sign-in (R/components/SignIn.tsx). To build: reuse POST /api/account/terms with version remail:<date> and app_metadata.remail_terms_version partial 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 (draft) ReMail To build: R/components/SignIn.tsx, R/lib/attribution.ts signupMetadata (add marketing_opt_out), shared /api/account/marketing gap 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 (draft) ReMail R/index.css line 1 imports Inter from fonts.googleapis.com; replace with @fontsource-variable/inter. Customer brand fonts: R/remail/brand/fonts.ts gap 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 pretends to send (logs, waits, shows "sent"); reached from HelpMenu gap 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 R/remail/editor/components/ecommerce.tsx (GitHub avatars), articles.tsx and socialProof.tsx (Jobs and Wozniak photos), brand and app-store assets in RM/apps/admin/public/remail/static/ gap 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, R/components/AccountModal.tsx, R/lib/devMode.ts (team@aeropage.io), RM/LICENSE.md ("Copyright 2020 Airtable"), /admin/design routes in R/App.tsx, RM/apps/admin/public/embed.js gap 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 P/routes/api.ts "ReMail LEGACY" routes (/keys/resend, /resend/send, /airtable/view, /airtable/records-by-ids, /keys/:keyId/verify-airtable); EXPECTED_REACH_INS in VK/cloudflare/proxy/scripts/remail-boundary.selfcheck.mjs gap 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, confirms unsubscribe and postal address are included, and says the send can't be undone remail/terms §9.5 ReMail R/remail/batching/ui/BatchProgressDialog.tsx (confirm step and "SEND ALL" gate) has counts and a typed confirmation, but no unsubscribe or address checks and no "can't be undone" wording partial 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 only. The global onError in P/index.ts logs whole error objects, which a legacy route error could fill (fixed by R-16) partial 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

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.