Skip to content

Commit 5b1df55

Browse files
V48 Gate 3 (specification-implementation): staging-testnet bring-up — environments model, bucket migration, automation script
Environments model pinned in the QA ledger: production-mainnet (bitcode.exchange + original Supabase) and staging-testnet as THE single non-production clone (testnet.bitcode.exchange Vercel environment, Supabase project mwugicjpxmrtctvjghjg, GitHub App bitcode-github-auxillary-stag-test); local develops against staging-testnet AMAP (localhost for every browser-facing hop). Programmatic bring-up: scripts/bringup-staging-testnet.sh (migrate | vercel-env | verify) — links + pushes all supabase/migrations, pushes the full Vercel env matrix (Supabase keys, shared wallet-OAuth HMAC secret, GitHub App credentials incl. the .pem via path) to the custom environment, and probes the custom-provider authorize + bucket. Secrets template committed as scripts/.env.staging-testnet.example; the filled file is gitignored. The asset-pack-artifacts bucket now ships as a MIGRATION (20260705190000_asset_pack_artifacts_bucket.sql): private bucket, 10MiB/.patch-only upload restriction, owner-only insert/select/delete RLS via foldername[1]=auth.uid() + extension='patch' — so every environment gets the artifact store from `db push` alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent dd42a0d commit 5b1df55

4 files changed

Lines changed: 194 additions & 7 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,4 @@ uapi/tailwind.config.js
9292
.vercel
9393
.env*.local
9494
.bitcode/pipeline-harness-runs/
95+
scripts/.env.staging-testnet

BITCODE_V48_QA.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,31 @@
77
- Posture: interactive local experiential QA of the first live commercial (testnet) experience; app runs locally (`pnpm -C uapi dev:remote`) against the staging Supabase project; wallet network testnet4
88
- Source-safety posture: source-safe evidence only; no secrets, protected source, provider payloads, wallet material, service-role keys, database credentials, or raw private prompts are serialized here.
99

10-
## Local-development auth environment (decided 2026-07-05)
11-
12-
A DEDICATED Supabase project for local development, replacing the shared
13-
staging project for local QA. Topology law: `localhost:3000` for as much as
14-
possible; `testnet.bitcode.exchange` only for functionality Supabase's cloud
15-
must reach server-to-server.
10+
## Environments (decided 2026-07-05)
11+
12+
TWO deployed environments plus local:
13+
14+
- **production-mainnet** — bitcode.exchange + the original Supabase project;
15+
mainnet posture.
16+
- **staging-testnet** — THE single non-production clone (Vercel environment
17+
with domain `testnet.bitcode.exchange`, Supabase project
18+
`mwugicjpxmrtctvjghjg`, GitHub App `bitcode-github-auxillary-stag-test`);
19+
testnet4 posture.
20+
- **local** — develops AGAINST staging-testnet as much as possible
21+
(`localhost:3000` for every browser-facing hop; `testnet.bitcode.exchange`
22+
only for flows requiring hosting, e.g. Supabase-cloud-reachable OAuth
23+
endpoints).
24+
25+
Bring-up automation: `scripts/bringup-staging-testnet.sh`
26+
(migrate | vercel-env | verify) with secrets in
27+
`scripts/.env.staging-testnet` (gitignored; template committed as
28+
`.example`). The `asset-pack-artifacts` bucket + RLS ship as migration
29+
`20260705190000_asset_pack_artifacts_bucket.sql`, so `migrate` covers
30+
storage setup too.
31+
32+
Topology law: `localhost:3000` for as much as possible;
33+
`testnet.bitcode.exchange` only for functionality Supabase's cloud must
34+
reach server-to-server.
1635

1736
- **Auth URL configuration (new project):** Site URL `http://localhost:3000`;
1837
Redirect URLs (exact, query-free per the allow-list law):
@@ -71,7 +90,11 @@ Project URL `https://mwugicjpxmrtctvjghjg.supabase.co`; GoTrue callback
7190
secret; Scopes `profile, wallet:bitcoin`; Allow users without email ON.
7291
4. Storage → Files: create bucket `asset-pack-artifacts` (PRIVATE) — the raw
7392
AssetPack artifact store; see the artifact-storage contract below.
74-
5. Database SSL enforcement: fine off for dev; enable before any production
93+
5. Auth → GitHub provider (GoTrue): client id/secret from the
94+
`bitcode-github-auxillary-stag-test` app's OAuth credentials; the app's
95+
callback/setup URLs point at `https://testnet.bitcode.exchange` (webhook
96+
likewise if used).
97+
6. Database SSL enforcement: fine off for dev; enable before any production
7598
promotion of this project's pattern.
7699

77100
**Vercel env (testnet.bitcode.exchange environment):**

scripts/bringup-staging-testnet.sh

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env bash
2+
# Bring up the STAGING-TESTNET environment (Supabase project mwugicjpxmrtctvjghjg
3+
# + Vercel testnet.bitcode.exchange). Idempotent; run pieces via flags.
4+
#
5+
# Prerequisites (interactive, once):
6+
# supabase login # or export SUPABASE_ACCESS_TOKEN
7+
# vercel login # then `vercel link` in repo root if unlinked
8+
#
9+
# Secrets file (NOT committed): scripts/.env.staging-testnet
10+
# copy scripts/.env.staging-testnet.example and fill values.
11+
#
12+
# Usage:
13+
# scripts/bringup-staging-testnet.sh migrate # link + push all migrations
14+
# scripts/bringup-staging-testnet.sh vercel-env # push env vars to the Vercel custom environment
15+
# scripts/bringup-staging-testnet.sh verify # read-only checks (auth authorize probe, bucket)
16+
# scripts/bringup-staging-testnet.sh all
17+
18+
set -euo pipefail
19+
20+
PROJECT_REF="mwugicjpxmrtctvjghjg"
21+
SUPABASE_URL="https://${PROJECT_REF}.supabase.co"
22+
SECRETS_FILE="$(dirname "$0")/.env.staging-testnet"
23+
# Vercel custom environment the testnet.bitcode.exchange domain is attached to.
24+
VERCEL_TARGET="${VERCEL_TARGET:-staging-testnet}"
25+
26+
require_secrets() {
27+
if [[ ! -f "$SECRETS_FILE" ]]; then
28+
echo "Missing $SECRETS_FILE — copy scripts/.env.staging-testnet.example and fill it." >&2
29+
exit 1
30+
fi
31+
# shellcheck disable=SC1090
32+
set -a; source "$SECRETS_FILE"; set +a
33+
}
34+
35+
do_migrate() {
36+
require_secrets
37+
: "${STAGING_TESTNET_DB_PASSWORD:?fill STAGING_TESTNET_DB_PASSWORD in $SECRETS_FILE}"
38+
supabase link --project-ref "$PROJECT_REF" --password "$STAGING_TESTNET_DB_PASSWORD"
39+
supabase db push --password "$STAGING_TESTNET_DB_PASSWORD"
40+
echo "Migrations pushed to $PROJECT_REF (includes asset-pack-artifacts bucket + RLS)."
41+
}
42+
43+
do_vercel_env() {
44+
require_secrets
45+
push_var() {
46+
local name="$1" value="$2"
47+
if [[ -z "$value" || "$value" == *"<"* ]]; then
48+
echo "SKIP $name (value not filled)"
49+
return
50+
fi
51+
# Remove existing value for the target first so re-runs update cleanly.
52+
vercel env rm "$name" "$VERCEL_TARGET" --yes >/dev/null 2>&1 || true
53+
printf '%s' "$value" | vercel env add "$name" "$VERCEL_TARGET"
54+
echo "SET $name -> $VERCEL_TARGET"
55+
}
56+
57+
push_var NEXT_PUBLIC_SUPABASE_URL "$SUPABASE_URL"
58+
push_var SUPABASE_URL "$SUPABASE_URL"
59+
push_var NEXT_PUBLIC_SUPABASE_ANON_KEY "${STAGING_TESTNET_ANON_KEY:-}"
60+
push_var SUPABASE_ANON_KEY "${STAGING_TESTNET_ANON_KEY:-}"
61+
push_var SUPABASE_PUBLISHABLE_KEY "${STAGING_TESTNET_PUBLISHABLE_KEY:-${STAGING_TESTNET_ANON_KEY:-}}"
62+
push_var NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY "${STAGING_TESTNET_PUBLISHABLE_KEY:-${STAGING_TESTNET_ANON_KEY:-}}"
63+
push_var SUPABASE_SERVICE_ROLE_KEY "${STAGING_TESTNET_SERVICE_ROLE_KEY:-}"
64+
push_var SUPABASE_SECRET_KEY "${STAGING_TESTNET_SECRET_KEY:-${STAGING_TESTNET_SERVICE_ROLE_KEY:-}}"
65+
push_var SUPABASE_JWT_SECRET "${STAGING_TESTNET_JWT_SECRET:-}"
66+
push_var BITCODE_BITCOIN_OAUTH_CLIENT_SECRET "${STAGING_TESTNET_WALLET_OAUTH_SECRET:-}"
67+
push_var BITCODE_BITCOIN_OAUTH_SUPABASE_CALLBACK_URL "${SUPABASE_URL}/auth/v1/callback"
68+
push_var NEXT_PUBLIC_BITCODE_BITCOIN_NETWORK "testnet4"
69+
push_var GITHUB_APP_ID "${STAGING_TESTNET_GITHUB_APP_ID:-}"
70+
push_var GITHUB_APP_CLIENT_ID "${STAGING_TESTNET_GITHUB_APP_CLIENT_ID:-}"
71+
push_var GITHUB_APP_CLIENT_SECRET "${STAGING_TESTNET_GITHUB_APP_CLIENT_SECRET:-}"
72+
push_var GITHUB_WEBHOOK_SECRET "${STAGING_TESTNET_GITHUB_WEBHOOK_SECRET:-}"
73+
if [[ -n "${STAGING_TESTNET_GITHUB_PRIVATE_KEY_PATH:-}" && -f "${STAGING_TESTNET_GITHUB_PRIVATE_KEY_PATH}" ]]; then
74+
push_var GITHUB_PRIVATE_KEY "$(cat "${STAGING_TESTNET_GITHUB_PRIVATE_KEY_PATH}")"
75+
else
76+
echo "SKIP GITHUB_PRIVATE_KEY (STAGING_TESTNET_GITHUB_PRIVATE_KEY_PATH unset or file missing)"
77+
fi
78+
echo "Done. Redeploy the $VERCEL_TARGET environment to pick up the new values."
79+
}
80+
81+
do_verify() {
82+
echo "== GoTrue custom-provider authorize probe (expects 302) =="
83+
curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
84+
"${SUPABASE_URL}/auth/v1/authorize?provider=custom:bitcode-bitcoin&redirect_to=${SUPABASE_URL}/auth/v1/callback" || true
85+
echo "== Storage bucket =="
86+
if [[ -n "${STAGING_TESTNET_SERVICE_ROLE_KEY:-}" ]]; then
87+
curl -sS "${SUPABASE_URL}/storage/v1/bucket/asset-pack-artifacts" \
88+
-H "apikey: ${STAGING_TESTNET_SERVICE_ROLE_KEY}" \
89+
-H "Authorization: Bearer ${STAGING_TESTNET_SERVICE_ROLE_KEY}" | head -c 400; echo
90+
else
91+
echo "SKIP bucket probe (fill STAGING_TESTNET_SERVICE_ROLE_KEY and re-run)"
92+
fi
93+
}
94+
95+
case "${1:-all}" in
96+
migrate) do_migrate ;;
97+
vercel-env) do_vercel_env ;;
98+
verify) require_secrets || true; do_verify ;;
99+
all) do_migrate; do_vercel_env; do_verify ;;
100+
*) echo "Usage: $0 [migrate|vercel-env|verify|all]" >&2; exit 1 ;;
101+
esac
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-- Raw AssetPack artifact store (decided 2026-07-05, BITCODE_V48_QA.md §
2+
-- "Raw AssetPack artifact storage"): artifacts are ALWAYS .patch files in the
3+
-- PRIVATE `asset-pack-artifacts` file bucket at
4+
-- `<user_id>/<run_id>/<option_id>.patch`. Owner-only RLS; buyers receive
5+
-- post-settlement signed URLs minted server-side (service role bypasses RLS).
6+
7+
insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
8+
values (
9+
'asset-pack-artifacts',
10+
'asset-pack-artifacts',
11+
false,
12+
10485760, -- 10 MiB per patch artifact
13+
array['text/x-patch', 'text/plain']
14+
)
15+
on conflict (id) do update
16+
set public = false,
17+
file_size_limit = excluded.file_size_limit,
18+
allowed_mime_types = excluded.allowed_mime_types;
19+
20+
do $$
21+
begin
22+
if not exists (
23+
select from pg_policies
24+
where schemaname = 'storage' and tablename = 'objects'
25+
and policyname = 'asset_pack_artifacts_owner_insert'
26+
) then
27+
create policy "asset_pack_artifacts_owner_insert"
28+
on storage.objects for insert to authenticated
29+
with check (
30+
bucket_id = 'asset-pack-artifacts'
31+
and (storage.foldername(name))[1] = auth.uid()::text
32+
and storage.extension(name) = 'patch'
33+
);
34+
end if;
35+
36+
if not exists (
37+
select from pg_policies
38+
where schemaname = 'storage' and tablename = 'objects'
39+
and policyname = 'asset_pack_artifacts_owner_select'
40+
) then
41+
create policy "asset_pack_artifacts_owner_select"
42+
on storage.objects for select to authenticated
43+
using (
44+
bucket_id = 'asset-pack-artifacts'
45+
and (storage.foldername(name))[1] = auth.uid()::text
46+
);
47+
end if;
48+
49+
if not exists (
50+
select from pg_policies
51+
where schemaname = 'storage' and tablename = 'objects'
52+
and policyname = 'asset_pack_artifacts_owner_delete'
53+
) then
54+
create policy "asset_pack_artifacts_owner_delete"
55+
on storage.objects for delete to authenticated
56+
using (
57+
bucket_id = 'asset-pack-artifacts'
58+
and (storage.foldername(name))[1] = auth.uid()::text
59+
);
60+
end if;
61+
end
62+
$$;

0 commit comments

Comments
 (0)