Skip to content

Commit 3219919

Browse files
wip v28
1 parent 26a8355 commit 3219919

10 files changed

Lines changed: 148 additions & 21 deletions

File tree

BITCODE_SPEC_V28_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Supabase/PostgreSQL data-health checks are part of the V28 acceptance apparatus,
6868

6969
May 14 staging-testnet verification added the first repository-owned RLS repairs after the dashboard-origin RLS bootstrap. `20260514173000_enable_pipeline_runs_rls.sql` enables RLS on `public.pipeline_runs`, and `20260514175000_enable_pipeline_runtime_rls.sql` enables RLS on server-managed pipeline runtime tables reported by Supabase Security Advisor. These preserve existing service-role pipeline posture and move runtime internals to deny-by-default for browser roles unless a narrower policy is later specified. The live daily data-health suite now passes 24/24 checks against the staging Supabase project, live ORM E2E tests can load `.env.local` directly for local QA parity with the CLI runner, and the advisory-style RLS probe returns no public tables with policies/browser grants while RLS is disabled.
7070

71+
May 14 live deployment QA proved the custom Bitcoin OAuth path through Supabase Auth after the project Site URL was corrected from localhost to `https://bitcode.exchange`. The Supabase custom provider created the expected `auth.users` and `auth.identities` rows for `custom:bitcode-bitcoin`; Bitcode then projected the same user into `public.user_profiles.settings.bitcodeProfile.walletBinding` and `public.user_connections.provider='leather'`. V28 QA SQL and data-health checks therefore read wallet binding through the canonical JSON settings path, not nonexistent flat `user_profiles.wallet_*` columns.
72+
7173
### Deterministic Model Boundary
7274

7375
V28 must remove the idea that a user can choose ledgerized Bitcode synthesis models.

BITCODE_V28_QA.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,13 @@ Deployment judgment:
853853
- Live staging data-plane readiness: pass baseline. `pnpm db:data-health:daily` checked 24 and passed 24 against `tkpyosihuouusyaxtbau`; the advisor-style RLS probe returned zero rows; saved-query verification still belongs in the first-run onboarding QA evidence.
854854
- Manual first-run onboarding may proceed after deployment env is confirmed; capture saved-query evidence during the pass.
855855
856+
2026-05-14 first live deployment onboarding evidence:
857+
858+
- The first Leather-auth callback initially fell back to `http://localhost:3000/?code=...` because Supabase Auth URL Configuration still had localhost as the Site URL. V28 now treats `https://bitcode.exchange` as the staging deployment Site URL and keeps `/tps/supabase/callback` in the allowlist.
859+
- After correcting the Site URL, Supabase Auth created `auth.users` and `auth.identities` rows with `provider='custom:bitcode-bitcoin'` and the Bitcoin testnet subject.
860+
- The matching Bitcode application projection exists in `public.user_profiles` under `settings.bitcodeProfile.walletBinding`, with the same user UUID as `auth.users.id`, and `public.user_connections` has a matching `provider='leather'` wallet row.
861+
- The saved `v28_qa_03_user_profiles_wallet_binding` query was corrected to read wallet binding from JSON settings rather than nonexistent flat `wallet_address`, `wallet_provider`, and `wallet_binding_status` columns.
862+
856863
## Issue Template
857864
858865
```md
@@ -935,10 +942,13 @@ Saved query name: `v28_qa_03_user_profiles_wallet_binding`
935942
select
936943
id,
937944
username,
938-
wallet_address,
939-
wallet_provider,
940-
wallet_binding_status,
941-
settings->'bitcodeProfile'->'walletBinding' as wallet_binding,
945+
settings #>> '{bitcodeProfile,walletBinding,address}' as wallet_address,
946+
settings #>> '{bitcodeProfile,walletBinding,provider}' as wallet_provider,
947+
settings #>> '{bitcodeProfile,walletBinding,status}' as wallet_binding_status,
948+
settings #>> '{bitcodeProfile,walletBinding,network}' as wallet_network,
949+
settings #>> '{bitcodeProfile,walletBinding,paymentAddress}' as payment_address,
950+
settings #>> '{bitcodeProfile,walletBinding,authAddress}' as auth_address,
951+
settings #> '{bitcodeProfile,walletBinding}' as wallet_binding,
942952
updated_at
943953
from public.user_profiles
944954
order by updated_at desc

packages/orm/src/data-health/checks.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,18 @@ export const DATA_HEALTH_CHECKS: DataHealthCheckDefinition[] = [
384384
WITH profile_wallets AS (
385385
SELECT
386386
id AS user_id,
387-
settings #>> '{walletBinding,provider}' AS provider,
388-
settings #>> '{walletBinding,address}' AS address
387+
coalesce(
388+
settings #>> '{bitcodeProfile,walletBinding,provider}',
389+
settings #>> '{walletBinding,provider}'
390+
) AS provider,
391+
coalesce(
392+
settings #>> '{bitcodeProfile,walletBinding,address}',
393+
settings #>> '{walletBinding,address}'
394+
) AS address
389395
FROM public.user_profiles
390-
WHERE settings ? 'walletBinding'
396+
WHERE
397+
settings #> '{bitcodeProfile,walletBinding}' IS NOT NULL
398+
OR settings ? 'walletBinding'
391399
),
392400
drift AS (
393401
SELECT

supabase/DATA_HEALTH.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ reusable checks:
109109
`supabase/queries/data_health_wallet_github_readiness.sql`
110110
- `Bitcode Data Health - BTD Ledger Reconciliation`:
111111
`supabase/queries/data_health_btd_ledger_reconciliation.sql`
112+
- `v28_qa_staging_hard_reset`:
113+
`supabase/queries/v28_qa_staging_hard_reset.sql` is a destructive,
114+
staging-only reset for early QA. It preserves schema/migrations, deletes
115+
Supabase Auth users, truncates Bitcode-owned public projection data, and
116+
reseeds `public.btd_supply_state`.
112117

113118
The script runner is authoritative for pass/fail automation. The saved queries
114119
are for operator inspection and QA narration.

supabase/config.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ file_size_limit = "50MiB"
4242

4343
[auth]
4444
enabled = true
45-
site_url = "http://localhost:3000"
46-
additional_redirect_urls = []
45+
site_url = "https://bitcode.exchange"
46+
additional_redirect_urls = [
47+
"https://bitcode.exchange/tps/supabase/callback",
48+
"https://bitcode.exchange/auxillaries/wallet",
49+
"http://localhost:3000/tps/supabase/callback",
50+
"http://localhost:3001/tps/supabase/callback",
51+
]
4752
jwt_expiry = 3600
4853
enable_refresh_token_rotation = true
4954
refresh_token_reuse_interval = 10

supabase/queries/data_health_wallet_github_readiness.sql

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,27 @@
44
WITH profile_wallets AS (
55
SELECT
66
id AS user_id,
7-
settings #>> '{walletBinding,provider}' AS profile_wallet_provider,
8-
settings #>> '{walletBinding,address}' AS profile_wallet_address,
9-
settings #>> '{walletBinding,status}' AS profile_wallet_status,
10-
settings #>> '{walletBinding,network}' AS profile_wallet_network,
7+
coalesce(
8+
settings #>> '{bitcodeProfile,walletBinding,provider}',
9+
settings #>> '{walletBinding,provider}'
10+
) AS profile_wallet_provider,
11+
coalesce(
12+
settings #>> '{bitcodeProfile,walletBinding,address}',
13+
settings #>> '{walletBinding,address}'
14+
) AS profile_wallet_address,
15+
coalesce(
16+
settings #>> '{bitcodeProfile,walletBinding,status}',
17+
settings #>> '{walletBinding,status}'
18+
) AS profile_wallet_status,
19+
coalesce(
20+
settings #>> '{bitcodeProfile,walletBinding,network}',
21+
settings #>> '{walletBinding,network}'
22+
) AS profile_wallet_network,
1123
updated_at AS profile_updated_at
1224
FROM public.user_profiles
13-
WHERE settings ? 'walletBinding'
25+
WHERE
26+
settings #> '{bitcodeProfile,walletBinding}' IS NOT NULL
27+
OR settings ? 'walletBinding'
1428
),
1529
wallet_connections AS (
1630
SELECT
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-- Saved query name: v28_qa_staging_hard_reset
2+
-- Purpose: destructive staging-only reset for early V28 QA.
3+
-- Do not run against production. This preserves schema/migrations and removes
4+
-- Supabase Auth users plus Bitcode-owned public projection rows.
5+
6+
begin;
7+
8+
delete from auth.users;
9+
10+
do $$
11+
declare
12+
truncate_sql text;
13+
begin
14+
select
15+
'truncate table ' ||
16+
string_agg(format('%I.%I', table_schema, table_name), ', ' order by table_name) ||
17+
' restart identity cascade'
18+
into truncate_sql
19+
from information_schema.tables
20+
where table_schema = 'public'
21+
and table_type = 'BASE TABLE'
22+
and table_name <> 'spatial_ref_sys';
23+
24+
if truncate_sql is not null then
25+
execute truncate_sql;
26+
end if;
27+
end $$;
28+
29+
insert into public.btd_supply_state (id)
30+
values ('global')
31+
on conflict (id) do nothing;
32+
33+
commit;

uapi/app/login/callback/LoginCallbackClient.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import TypingAnimation from '@/components/base/bitcode/typing-animation';
77

88
interface LoginCallbackClientProps {
99
code: string;
10+
codeKind?: 'oauth_code' | 'token_hash' | 'none';
1011
/** Where to redirect after session established ("/" by default) */
1112
nextPath?: string;
1213
}
@@ -15,13 +16,17 @@ interface LoginCallbackClientProps {
1516
* Client-side interactive UI for the login callback route.
1617
* Includes animation effects and copy-to-clipboard.
1718
*/
18-
export default function LoginCallbackClient({ code, nextPath = '/' }: LoginCallbackClientProps) {
19+
export default function LoginCallbackClient({
20+
code,
21+
codeKind = 'none',
22+
nextPath = '/',
23+
}: LoginCallbackClientProps) {
1924
const [copied, setCopied] = useState(false);
2025
const [redirecting, setRedirecting] = useState(false);
2126
const containerRef = useRef<HTMLDivElement>(null);
2227

2328
// Determine if this is an OTP flow (magic-link) or an OAuth redirect
24-
const isOtpFlow = Boolean(code && code.trim().length > 0);
29+
const isOtpFlow = codeKind === 'token_hash' && Boolean(code && code.trim().length > 0);
2530

2631
// Track mouse for subtle effect
2732
const mx = useMotionValue(0);
@@ -117,6 +122,19 @@ export default function LoginCallbackClient({ code, nextPath = '/' }: LoginCallb
117122
// `#access_token=…`). Supabase doesn’t parse the hash automatically in
118123
// the browser SDK, so we do it manually and call `setSession()`.
119124
const hash = typeof window !== 'undefined' ? window.location.hash || '' : '';
125+
if (codeKind === 'oauth_code' && code) {
126+
try {
127+
const { error } = await supabase.auth.exchangeCodeForSession(code);
128+
if (error) throw error;
129+
complete();
130+
return;
131+
} catch (exchangeError) {
132+
const message = exchangeError instanceof Error ? exchangeError.message : String(exchangeError);
133+
window.location.href = `/?loginError=server_error&loginErrorDescription=${encodeURIComponent(message)}`;
134+
return;
135+
}
136+
}
137+
120138
if (hash.startsWith('#')) {
121139
const p = new URLSearchParams(hash.slice(1));
122140
const access_token = p.get('access_token');
@@ -154,7 +172,7 @@ export default function LoginCallbackClient({ code, nextPath = '/' }: LoginCallb
154172
return () => {
155173
cleanup?.();
156174
};
157-
}, [nextPath]);
175+
}, [code, codeKind, nextPath]);
158176

159177
const handleCopy = () => {
160178
if (!code) return;

uapi/app/page.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Metadata } from 'next';
2+
import { redirect } from 'next/navigation';
23

34
import PublicShellFrame from './(root)/components/PublicShellFrame';
45
import MarketingLandingPage from './(root)/components/MarketingLandingPage';
@@ -12,7 +13,32 @@ export const metadata: Metadata = {
1213
},
1314
};
1415

15-
export default function Home() {
16+
type SearchParams = Record<string, string | string[] | undefined>;
17+
18+
function appendSearchParams(target: URLSearchParams, searchParams: SearchParams) {
19+
for (const [key, value] of Object.entries(searchParams)) {
20+
if (Array.isArray(value)) {
21+
for (const entry of value) {
22+
if (entry) target.append(key, entry);
23+
}
24+
} else if (value) {
25+
target.set(key, value);
26+
}
27+
}
28+
}
29+
30+
function hasSupabaseCallbackParams(searchParams: SearchParams) {
31+
return Boolean(searchParams.code || searchParams.token_hash || searchParams.error || searchParams.error_description);
32+
}
33+
34+
export default function Home({ searchParams = {} }: { searchParams?: SearchParams }) {
35+
if (hasSupabaseCallbackParams(searchParams)) {
36+
const params = new URLSearchParams();
37+
appendSearchParams(params, searchParams);
38+
if (!params.has('next')) params.set('next', '/auxillaries/wallet');
39+
redirect(`/tps/supabase/callback?${params.toString()}`);
40+
}
41+
1642
return (
1743
<PublicShellFrame>
1844
<MarketingLandingPage />

uapi/app/tps/supabase/callback/page.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@ export default function SupabaseCallbackPage({ searchParams }: { searchParams: S
1919
}
2020

2121
const nextPath = firstParam(searchParams.next) || '/';
22-
const code = firstParam(searchParams.code) || firstParam(searchParams.token_hash);
23-
return <LoginCallbackClient code={code} nextPath={nextPath} />;
22+
const authCode = firstParam(searchParams.code);
23+
const tokenHash = firstParam(searchParams.token_hash);
24+
return (
25+
<LoginCallbackClient
26+
code={authCode || tokenHash}
27+
codeKind={authCode ? 'oauth_code' : tokenHash ? 'token_hash' : 'none'}
28+
nextPath={nextPath}
29+
/>
30+
);
2431
}
25-

0 commit comments

Comments
 (0)