docs(oddkit-sales): unified account launch plan + MCP bearer-token handoff#214
Conversation
…ndoff Two canon artifacts authored in one planning session: - docs/oddkit/sales/unified-account-launch-plan.md - the customer-surface build spec for the single Lovable session that ships every tier at once (Personal, Pro, Team, Team Pro, Mission, FA, Not-sure). Supabase + Lovable + Stripe substrate. Includes data model, application flows, FA state machine + 10-slot cap, referral system, ToS + privacy, 6B Table A, internal commitments (metering-blind acknowledged internally, never advertised; grandfathering as permanent policy). - odd/handoffs/2026-05-16-mcp-bearer-token-middleware.md - the separate Worker-repo execution slice that wires validateBearerToken() into klappy/oddkit and klappy/aquifer-mcp. 6B Table B with OAuth-MCP Bide tripwire. Minimal middleware today (~30 LOC); tier gating waits. Authored against canon/constraints/borrow-evaluation-before-implementation with falsifiable 6B tables in both docs. Companion build session for the Lovable surface opens after this lands; the Worker-repo middleware slice opens as a separate execution session in klappy/oddkit and klappy/aquifer-mcp.
Canon Quality — Frontmatter Schema ✅All 41 file(s) in Validator: |
Canon Quality —
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
Bugbot Autofix prepared fixes for all 4 issues found in the latest run.
- ✅ Fixed:
base64urlencoding unavailable in current PostgreSQL- Replaced the PG19-only
base64urlargument with a portablereplace(replace(rtrim(encode(...,'base64'),'='),'+','-'),'/','_')expression that produces an equivalent URL-safe string on Supabase's current PostgreSQL versions.
- Replaced the PG19-only
- ✅ Fixed: JWT entitlements read from wrong claim path
- Updated the middleware sketch to read entitlements from
payload.app_metadata?.entitlementsand clarified in the launch plan thatraw_app_meta_datasurfaces in the JWT under theapp_metadataclaim, aligning the JWT and PAT paths.
- Updated the middleware sketch to read entitlements from
- ✅ Fixed: PAT hash index unusable with bcrypt comparison
- Replaced the unusable
idx_pats_hashwithidx_pats_prefixand added ap.prefix = LEFT(token, 16)filter tovalidate_patso the bcrypt verify only runs against the single indexed candidate row.
- Replaced the unusable
- ✅ Fixed: SECURITY DEFINER function missing search_path restriction
- Added
SET search_path = extensions, publicto thevalidate_patSECURITY DEFINER function so unqualifiedcrypt()cannot be shadowed by an attacker-controlled schema (CVE-2007-2138).
- Added
You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit b11ac19. Configure here.
| 'church-or-ministry-network','other' | ||
| )), | ||
| heard_via TEXT, | ||
| referral_code TEXT UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(6),'base64url'), |
There was a problem hiding this comment.
base64url encoding unavailable in current PostgreSQL
High Severity
The profiles table uses encode(gen_random_bytes(6), 'base64url') as a default, but base64url is a PostgreSQL 19 feature (releasing September 2026). Supabase currently runs PostgreSQL 15–17, so this DEFAULT expression will raise a runtime error on every profile insert, blocking account creation entirely. The fix is to use 'base64' with manual URL-safe character replacement, or 'hex' encoding.
Reviewed by Cursor Bugbot for commit b11ac19. Configure here.
| }); | ||
| return { | ||
| user: { id: payload.sub as string, email: payload.email as string }, | ||
| entitlements: (payload.entitlements as string[]) ?? [], |
There was a problem hiding this comment.
JWT entitlements read from wrong claim path
Medium Severity
The middleware reads payload.entitlements from the JWT, but the launch plan writes entitlements to auth.users.raw_app_meta_data, which Supabase surfaces in the JWT as app_metadata.entitlements, not as a top-level claim. This means the JWT path will always resolve entitlements to [] while the PAT path (which reads raw_app_meta_data->'entitlements' directly) returns them correctly — an inconsistency between the two auth methods.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b11ac19. Configure here.
| revoked_at TIMESTAMPTZ, | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| ); | ||
| CREATE INDEX idx_pats_hash ON pats(hash) WHERE revoked_at IS NULL; |
There was a problem hiding this comment.
PAT hash index unusable with bcrypt comparison
Medium Severity
The idx_pats_hash index on pats(hash) cannot be used by validate_pat because the query p.hash = crypt(token, p.hash) computes a per-row bcrypt value — PostgreSQL cannot index-seek on it. Every PAT validation performs a full scan of all non-revoked PATs, each requiring an expensive bcrypt computation. The pats table already stores a prefix column that could narrow to a single candidate row before the bcrypt verify, but validate_pat doesn't use it.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b11ac19. Configure here.
| WHERE p.revoked_at IS NULL | ||
| AND p.hash = crypt(token, p.hash); | ||
| END; | ||
| $$; |
There was a problem hiding this comment.
SECURITY DEFINER function missing search_path restriction
Medium Severity
The validate_pat function is declared SECURITY DEFINER but doesn't include SET search_path = extensions, public (or similar). It calls unqualified crypt() from pgcrypto, which makes it vulnerable to CVE-2007-2138 — a user with schema-creation privileges could shadow crypt with a malicious function that captures plaintext PAT tokens while running with the function owner's elevated privileges.
Reviewed by Cursor Bugbot for commit b11ac19. Configure here.


What this lands
Two canon artifacts from a planning session that resolved the customer-surface build:
docs/oddkit/sales/unified-account-launch-plan.md— the Lovable build spec. One session ships every tier at once (Personal, Pro, Team, Team Pro paid; Mission + FA application-gated; Not-sure as fallback). Supabase + Lovable + Stripe substrate. Includes:profiles,tier_interests,fa_applications,mission_applications,pats,anthropic_keys,referrals,credit_ledger)klappy://canon/constraints/borrow-evaluation-before-implementationodd/handoffs/2026-05-16-mcp-bearer-token-middleware.md— the Worker-repo execution slice. Specifies the sharedvalidateBearerToken()middleware forklappy/oddkitandklappy/aquifer-mcp(and future TruthKit). Validates Supabase JWTs (viajose+ JWKS) and PATs (via Supabase RPC +pgcrypto). 6B Table B with the OAuth-MCP Bide tripwire (2+ major MCP clients shipping interactive OAuth-MCP UX).Why now
Canon-first: governance article merges before the code PR opens. Once this lands, the Lovable build session opens with a one-line prompt that points Lovable (which has the oddkit MCP server in its connector list) at
klappy://docs/oddkit/sales/unified-account-launch-plan. The doc carries the weight; the prompt stays short. Prompt-over-code applied to product development.Gate
Authored after a session of mode-disciplined planning including: 6B evaluation, reality verification against the live
oddkit.devpage (caught my own misreading of FA terms — they are free for 3 years, not paid-with-discount), and explicit operator decisions on metering visibility, grandfathering policy, monthly+annual SKUs, and referral scope.Note
Low Risk
Low risk: this PR only adds documentation/spec artifacts and does not change runtime code, schemas, or production configuration.
Overview
Adds two new canon docs that define upcoming implementation work without changing code.
docs/oddkit/sales/unified-account-launch-plan.mdspecifies the one-session Lovable build for the Oddkit customer surface (Supabase auth/DB + Stripe Checkout/Portal/webhooks), including the target schema, routes, application/referral flows, and explicit out-of-scope constraints.odd/handoffs/2026-05-16-mcp-bearer-token-middleware.mdprovides an execution handoff for Worker repos to add sharedvalidateBearerToken()middleware supporting Supabase JWT (JWKS viajose) and PAT validation (Supabase RPC +pgcrypto), with wiring and acceptance criteria.Reviewed by Cursor Bugbot for commit b11ac19. Bugbot is set up for automated code reviews on this repo. Configure here.