Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Complete production-grade horse management desktop app with editable fields, OCR, SQLite, and auto-update integration.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Premium operational command infrastructure for modern horse and ranch operations: command files, proof control, title and transfer posture, care status, buyer movement, operating ledger, field conditions, and ranch control across web and mobile.
• Web: Vite + React, deployed through Vercel
• Mobile: Capacitor wrapping the same React application for iOS and Android
• Persistence: local-first IndexedDB with optional Supabase auth, storage, and workspace sync
• Billing: Stripe Billing and Checkout Sessions when server environment variables are configuredREADME 전문 보기 ↗Premium operational command infrastructure for modern horse and ranch operations: command files, proof control, title and transfer posture, care status, buyer movement, operating ledger, field conditions, and ranch control across web and mobile.
git clone https://github.com/Thunderhorse891/XBAR.git
cd XBAR
npm install
npm run dev
| Script | Description |
|---|---|
npm run dev | Start the web app locally |
npm run build | Typecheck, build the app bundle, then generate the public site |
npm run preview | Serve dist with production-parity routing (serve-dist) |
npm run test | Prepare the Supabase schema, typecheck, and run unit tests |
npm run test:e2e | Run Playwright end-to-end tests |
npm run test:prod-smoke | Build dist and smoke-test the production bundle in a browser |
npm run lint | Run ESLint across the repo |
npm run format:check | Verify Prettier formatting (CI-gated) |
npm run supabase:prepare | Generate the executable, idempotent production Supabase schema |
npm run mobile:sync | Build and sync the app into Capacitor targets |
/ is the public marketing site: complete static HTML generated at
build time by scripts/build-marketing.mjs (home, features, pricing,
solutions, resources, demo, privacy, terms). Every page ships unique
server-generated metadata, a self-referencing canonical, and JSON-LD, and
never loads the application bundle./app/* is the authenticated application: the SPA shell is emitted as
dist/app.html, rewritten under /app/* by vercel.json, and marked
noindex (meta tag + X-Robots-Tag). The React Router basename is /app
(src/lib/routeCanon.ts).vercel.json also 308-redirects legacy paths (/login, /landing,
/horses, /profiles/:id, …) into their new homes and redirects
www.xbar.app to the canonical xbar.app host.sitemap.xml is generated with the marketing pages only; robots.txt
disallows /app, /api/, /profiles/.scripts/serve-dist.mjs mirrors this routing locally (npm run preview)
and backs the prod-smoke Playwright suite.scripts/build-mobile.mjs), which skip marketing generation.npm run mobile:add:ios or npm run mobile:add:android.npm run mobile:sync.npm run mobile:open:ios or npm run mobile:open:android.Capacitor configuration lives in capacitor.config.ts.
--font-ui / --font-display in src/index.css and loaded once
from index.html — no per-file font importssrc/routes/xbarCommandSystem.css and src/routes/commandCenterLocal.csssrc/routes/interactionSystem.csssrc/index.cssscripts/marketing/site.css (single
stylesheet, no application CSS)docs/PRODUCTION-AUDIT-2026-07.md)No button may report success without persistent evidence. Every create/update
action in the UI calls a real store mutation and reports the store's actual
result; navigation actions are labeled as navigation ("Open …"), not as
creation. tests/marketingSite.test.ts additionally blocks unverifiable
social-proof claims from the public site.
Each product surface should answer the same operational sequence:
Daily operations should not collapse into repeated dashboards. Command Center, Command Files, Title & Transfer, Proof Vault, Care Status, Buyer Desk, Operating Ledger, Ranch Assets, Field Conditions, and Ranch Control each require a distinct layout silhouette and workflow.
Copy .env.example to .env.local for local development. Configure the same variable names in Vercel Preview and Production. Never expose server secrets with a VITE_ prefix.
Required for browser cloud auth and sync:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYOptional, and empty by default:
VITE_AUTH_OAUTH_PROVIDERS — comma-separated list of google, facebook,
apple. Controls which third-party sign-in buttons the login screen draws.
Leave it unset until a provider is enabled in Supabase → Authentication →
Providers and its OAuth client is registered with the provider itself.
Supabase answers a redirect for anything else with HTTP 400 Unsupported provider: provider is not enabled, which a customer experiences as a button
that does nothing. Native store builds never show these buttons, because a
web OAuth redirect cannot complete inside the app WebView.Required for managed Stripe billing and webhook reconciliation:
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_PRICE_ID_STARTERSTRIPE_PRICE_ID_PROFESSIONALSTRIPE_PRICE_ID_RANCH_OPSSTRIPE_PRICE_ID_ENTERPRISESTRIPE_PRICE_ID_*_ANNUAL (one per tier, e.g. STRIPE_PRICE_ID_STARTER_ANNUAL) — annual billing; checkout refuses annual until these are setPUBLIC_APP_URLOwner/QA access (all optional, all off by default):
XBAR_COMP_EMAILS (server) — the allowlist that grants full entitlements for
API-checked cloud actions. It is keyed on email rather than workspace, always
grants Enterprise rather than a chosen tier, and is applied by the API only —
the database limit triggers read workspace_subscription_profiles and never
see it. So it is a preview and QA tool, not a way to comp a paying
customer: the API would report Enterprise while seat, storage and commercial
writes were still refused at the stored tier.VITE_XBAR_COMP_EMAILS (client) — the matching list so the UI shows what the
server will honour. Set both to the same value; setting only the client one
shows tiers the server refuses.VITE_XBAR_LOCAL_OWNER_MODE (client) — local tier preview for a machine with
no cloud account. Not available in production: it is compiled in at build
time and a production bundle refuses it even when the variable is set, so it
cannot be switched on from a URL, from localStorage, or from anything a
visitor can edit. It previews the UI only — cloud actions are still authorized
against the real account.Tier preview never writes to a workspace's real subscription, so returning to the real plan is switching the overlay off rather than repairing data.
Optional browser configuration is documented in .env.example, which states for
every variable whether it is client-safe or server-only, whether it is needed
now or later, and what the app does when it is absent.
Neither is required. With both absent the app runs locally: records stay in the browser, and the billing screen shows every tier, price, limit and feature list while saying Billing not configured yet. No checkout opens, no subscription record is created, and no identifier is invented — missing configuration is reported, never faked.
September 28 update: the billing-period, trial, private-media, trial-history,
invalid-date and atomic-event corrections are applied on xbar-records as
20260928153909_xbar_launch_billing_trial_private_media_reconciliation.
The internal trial grant correction is recorded as
20260928154229_trial_helper_private. See
the repair record for the six source files,
verification and recovery boundaries. Do not replay those files merely because
their original filenames do not appear individually in the hosted ledger.
Run supabase/checks/launch-readiness.sql for a read-only schema check;
/api/health checks configuration and does not prove schema compatibility.
On xbar-records (uxvwfepyothlakhqazwv), the Supabase migration ledger checked
on September 10, 2026 records steps 1–5 below as applied on September 4. Step 6
was applied on September 10 as 20260910173613_private_share_token_fail_closed;
both deployed token guards and the validated constraint were checked afterward.
Step 7 was applied as 20260911003739_share_release_selected_row (September 11
UTC / September 10 Chicago), with a failing-before/passing-after rollback test.
Step 8 was applied as 20260911005818_workspace_access_policies, with live
authenticated-role permission and invitation checks; all fixture records rolled back.
Do not rerun the billing data reconciliation merely because this checklist exists.
Migration history establishes recorded execution, not a successful customer checkout.
Use the target project's migration ledger and deployed definitions to establish deployment state. File comments and migration counts can both become stale; neither establishes what ran on another project. A recorded name also does not prove that a subsequently edited file is identical to the executed SQL.
For another project, check its migration history first. The order matters, and it is carried by the version prefixes rather than by convention — Supabase takes the digits before the first underscore as the migration version, so each file needs its own:
20260820_entitlement_helpers_honor_inactive.sql — schema. Makes the
database's limit helpers agree with the API about which billing states keep
a purchased tier. Safe to re-run: both functions are create or replace
with unchanged signatures, and no trigger is detached.20260821_reconcile_legacy_manual_billing.sql — data. Reconciles rows
the previous status mapper stored as Manual Billing when the subscription
had actually canceled, paused, or never completed its first payment. Without
this, step 1 changes nothing for those workspaces, because Manual Billing
is correctly still an entitled state.20260822_restrict_anon_rpc_surface.sql — grants. Closes the unauthenticated
RPC surface left by PostgreSQL's default EXECUTE grant to PUBLIC.20260826_checkout_session_lock.sql — schema. Adds the column that
serializes Checkout Session creation per workspace, so two concurrent
requests cannot each create a billable subscription session. Additive and
independent of the other three: one nullable column and one partial index,
no rewrite, and its order in the sequence does not matter.20260827_subscription_event_ordering.sql — schema. Adds
stripe_event_created_at and the xbar_apply_subscription_event function
that applies a billing event atomically, so a retried event Stripe created
earlier cannot overwrite a newer one. Events sharing a created second are
admitted — a plan change emits several — except that a tied entitlement
adopted from a SIBLING subscription loses, which is what stops one of two
simultaneously canceled subscriptions writing back a stale Active
snapshot of the other. Only that write is speculative: the sibling list is
read before the lock. An event about its own subscription still wins a tie,
so a re-subscription completed in the same second as a cancellation is not
thrown away. Additive: one nullable column, one index, one
function, no backfill.20260910173613_private_share_token_fail_closed.sql — security. A
Private Token listing whose stored token is empty — which is what the
column defaults produce — resolved for any caller who knew only the
share_path, handing over the horse payload, its documents and its
ownership record. Patches xbar_resolve_public_listing_legacy and
xbar_track_public_share_view, and adds a CHECK so no such row can be
created again. Order does not matter relative to the others; it shares
nothing with them.20260911003739_share_release_selected_row.sql — security. Apply after step 6.
Requires Live state and seller release approval on the exact listing selected
by the resolver and tracker. A released sibling sharing a path must not
authorize an unreleased draft. Preserves token guards and existing grants.For migrations still missing from the target project, apply them one at a time, not with a single supabase db push. That command
applies every pending migration in one go, which would run the data
reconciliation before anyone had read its dry-run.
# 1. schema only — safe to apply directly
psql "$DATABASE_URL" -f supabase/migrations/20260820_entitlement_helpers_honor_inactive.sql
# 2a. READ FIRST: the dry-run at the top of the file lists every candidate row
# and its disposition. Run that query and read every row.
# 2b. The migration is inert on its own — applying it without the setting below
# changes nothing and prints "reconciliation SKIPPED". Re-run it with:
# Omit either list entirely if it is empty; a literal '<uuid>,<uuid>'
# aborts the migration rather than being ignored.
psql "$DATABASE_URL" \
-c "set xbar.reconcile_confirmed = 'yes'" \
-c "set xbar.reconcile_exclude = '<uuid>,<uuid>'" \
-c "set xbar.reconcile_terminal = '<uuid>,<uuid>'" \
-f supabase/migrations/20260821_reconcile_legacy_manual_billing.sql
# 2c. Confirm the outcome. The AFTER APPLYING block at the bottom of that file
# lists every remaining Manual Billing row with its disposition. It reads
# xbar.reconcile_exclude, so restate the same list if you run it in a new
# session, and expect the rows you excluded to come back Stripe-backed and
# still Manual Billing — that is what excluding them did.
# 2d. Clear all three so they cannot colour later work in the same session:
psql "$DATABASE_URL" \
-c "reset xbar.reconcile_confirmed" \
-c "reset xbar.reconcile_exclude" \
-c "reset xbar.reconcile_terminal"
# 3a. grants — STAGING FIRST. This one revokes; a missing grant is a broken
# read for every signed-in user, so it is proved somewhere disposable.
psql "$STAGING_DATABASE_URL" -f supabase/migrations/20260822_restrict_anon_rpc_surface.sql
node scripts/verify-rpc-surface.mjs "$STAGING_DATABASE_URL"
# 3b. Then exercise staging by hand — steps 3 and 4 of the HOW TO APPLY block
# inside that migration. The verifier proves the anon surface shrank; only
# loading a workspace and running a document upload proves nothing that
# should still work broke.
# 3c. ONLY THEN production, ideally in a low-traffic window. Skipping this line
# leaves production on the default unauthenticated EXECUTE grants — every
# SECURITY DEFINER function, the unmaintained legacy listing resolver
# included — which is the whole of what this migration exists to close.
psql "$DATABASE_URL" -f supabase/migrations/20260822_restrict_anon_rpc_surface.sql
node scripts/verify-rpc-surface.mjs "$DATABASE_URL"
# 4. checkout lock — additive, safe to apply directly, order does not matter
psql "$DATABASE_URL" -f supabase/migrations/20260826_checkout_session_lock.sql
# 5. billing event ordering — additive, safe to apply directly
psql "$DATABASE_URL" -f supabase/migrations/20260827_subscription_event_ordering.sql
# 6. private share token fail-closed — the security one. Additive: two function
# replacements and one CHECK added NOT VALID, so it cannot fail on rows that
# already exist.
psql "$DATABASE_URL" -f supabase/migrations/20260910173613_private_share_token_fail_closed.sql
# 6a. The migration validates the constraint ONLY if no offending row exists,
# and otherwise raises a warning naming the count. Read that warning. New
# inserts and updates are checked either way, and the patched functions
# already deny the old links, so there is no rush — but the constraint is
# not fully enforced until it is validated.
# 6b. Clean up any offending rows with the owner's authorization, then validate
# separately. Archiving is exempt from the CHECK and is the safer of the
# two paths: re-issuing hands a broken share a WORKING token on the way to
# retiring it, which is the one outcome this fix exists to prevent. Do not
# delete listings or make them public merely to pass validation.
# alter table public.shared_listings validate constraint <name>;
# 6c. Prove it on a throwaway database rather than trusting the diff. Load the
# migrated function shape, then:
# psql "$THROWAWAY_URL" -f supabase/checks/share-token-fail-closed.sql
# Expect: no token refused, wrong token refused, correct token resolved,
# public link resolved. Anything else, including BOTH controls refusing,
# means the check did not exercise what it claims — see its header.
# 7. Bind release approval to the selected listing (after step 6).
psql "$DATABASE_URL" -f supabase/migrations/20260911003739_share_release_selected_row.sql
# Optional deployed-schema check with all fixtures rolled back:
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/share-token-live-rollback.sql
# 8. Workspace access and safe invitation acceptance.
psql "$DATABASE_URL" -f supabase/migrations/20260911005818_workspace_access_policies.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/workspace-access-live-rollback.sql
# 9. Billing period on subscription profiles — additive, safe to apply directly.
# MUST run after #5 (the filename date keeps that order): it replaces
# xbar_apply_subscription_event with the 15-argument signature. The webhook
# passes p_billing_period and fails closed — no entitlement is written —
# until this is applied. Needs the owner's explicit approval (contract
# rule 15); it is NOT applied by the change that wrote it.
psql "$DATABASE_URL" -f supabase/migrations/20260924120000_billing_period.sql
# 10. Preserve the trial record inside the locked RPC — function replace in a
# single transaction, safe to apply directly. MUST run after #9 (the
# filename date keeps that order): it replaces xbar_apply_subscription_event
# with the same 15-argument signature, merging payload.trial from the
# locked current row so a trial starting between the webhook's pre-lock
# SELECT and the RPC write is not erased. Until this is applied, the
# webhook's own carry-forward covers only the sequential case. Needs the
# owner's explicit approval (contract rule 15); it is NOT applied by the
# change that wrote it.
psql "$DATABASE_URL" -f supabase/migrations/20260924223000_preserve_trial_in_event_rpc.sql
# Merge trial history from the actual conflict row, not an earlier read.
# Validate the trial-event-database CI contract and obtain explicit production
# migration approval before applying. The Python/schema fixtures are CI-only.
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260926003000_atomic_trial_event_merge.sql
# 10. EXPAND before deploying the workspace-path client. Retains uploader paths
# for older app versions. Includes authenticated RLS checks with rolled-back
# fixtures. Run atomically so a failed assertion rolls back policy changes.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260912055000_expand_document_storage_paths.sql
# 10. Trial entitlement — additive, safe to apply directly, order does not
# matter. Replaces the two entitlement helpers so an active 14-day
# Professional trial (payload.trial, written by /api/trial/start) resolves
# to Professional in the database triggers. A trial only ever raises a
# baseline workspace — paid and comped tiers are untouched.
psql "$DATABASE_URL" -f supabase/migrations/20260924130000_trial_entitlement.sql
# Reject impossible trial dates without aborting database capacity checks.
# Validate both supabase/checks/trial-*.sql files on a disposable database
# first. Production application still requires the owner's explicit approval.
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260926000000_trial_dates_fail_closed.sql
# Internal trial predicate: remove Supabase's default public execution grants.
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260928120000_trial_helper_private.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260928163000_reminder_email_delivery_claims.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260928170000_reminder_declined_retry.sql
# Prove it on a throwaway database rather than trusting the diff. Load the
# migration first, then:
# psql "$THROWAWAY_URL" -f supabase/checks/trial-entitlement.sql
# Expect: the check's notice line and no exception. Apply to production only
# with the owner's explicit approval (production engineering contract §15).
The uploader-keyed contract for documents is now part of step 11. The live
project completed the document-storage expansion; see
storage rollout evidence. 20260912060000 was
the original contract phase for documents. Step 11's contract
(20261001090100) supersedes it and removes the same uploader-keyed branches
from both buckets. Never run it after step 11: it rebuilds the document
policies WITH the uploader branch and would silently undo that contract. On a
database that has had neither, running it is optional and only ever before
step 11; once step 11 has begun it refuses to run. Its check script describes
that intermediate state and nothing later.
# Superseded by step 11. Optional, and only BEFORE step 11's contract.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260912060000_workspace_keyed_document_storage.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/document-storage-live-rollback.sql
# 10. horse-media goes PRIVATE. Deploy the signed-URL client and the
# token-gated /api/buyer/media endpoint FIRST -- they work while the bucket
# is still public (createSignedUrl succeeds on a public bucket), so the
# flip is hitless. Applying this migration is Erin's explicit ops step
# (production engineering contract #15): existing public media URLs stop
# resolving the moment it runs, and rollback instructions live in the
# migration header.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20260924134000_horse_media_private_signed_urls.sql
# 11. Workspace-keyed storage (audit F02), in two phases.
# EXPAND first -- before the client that files photos under the workspace
# id is deployed. It closes the gallery-listing read and adds workspace
# reads and uploadMedia-gated writes, while keeping the uploader-keyed
# branches so a tab still running the previous bundle keeps working.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261001090000_workspace_keyed_storage_expand.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/workspace-keyed-storage-expand.sql
# Then deploy the client. CONTRACT only once it is live: it removes every
# uploader-keyed branch from horse-media and horse-documents. Moves no
# data; first confirm no object is still uploader-keyed:
# select bucket_id, count(*) from storage.objects
# where bucket_id in ('horse-media','horse-documents')
# and split_part(name,'/',1) not in (select id::text from public.workspaces)
# group by 1;
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261001090100_workspace_keyed_storage_contract.sql
# Prove the final state under real RLS (synthetic users, all rolled back):
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/workspace-keyed-storage.sql
# 12. Seat reservations (audit F05). Accepting an invitation no longer counts
# its own reservation as a second seat, so the last reserved seat on a plan
# can be accepted; seat checks are serialized per workspace. CREATE OR
# REPLACE of the trigger function only -- no data, no new grants.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261002090000_seat_reservation_consumed_on_accept.sql
# Prove it with the real trigger and acceptance RPC (all rolled back):
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/seat-reservation.sql
# 13. Account deletion is decided at the database (audit F03). Adds the
# deletion hold (a hold refuses new members while the owner's account is
# deleted, under the seat lock) and the durable deletion receipt. Additive:
# two tables, three service-role functions, two triggers. Apply BEFORE
# deploying the /api/account-delete that calls the hold RPC -- without it,
# account deletion refuses with "Nothing was changed".
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261002100000_account_deletion_hold.sql
psql -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/checks/account-deletion-hold.sql
# Cleanup that did not finish stays on record:
# select user_id, status, storage_leftovers, requested_at
# from public.account_deletion_receipts where status <> 'complete';
# 14. Staff saves reach the cloud (audit F04). Write policies per record
# table, following the role matrix, beside the existing manager policies;
# a guard limits a Medical Lead to a horse's medical and document fields;
# invited Admins can save the ranch profile. Additive only. Apply BEFORE
# deploying the client that lets staff saves through.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261002120000_staff_write_policies.sql
# Proven under real RLS in CI (.github/workflows/records-database.yml).
# staff-writes.sql deletes rows inside its rolled-back transaction; run it
# against a disposable database, not production.
# 15. DRAFT RELEASE GATE: request-token account-deletion fence and billing
# safeguards. Requires explicit production migration approval. Drain/stop
# deletion requests first; old deletion handlers deliberately refuse after
# this migration until the matching handler is deployed. Read
# docs/ACCOUNT-DELETION-BILLING-SAFETY.md before applying or rolling back.
# The command is documented for approved rollout; it has NOT been applied.
psql -1 -v ON_ERROR_STOP=1 "$DATABASE_URL" -f supabase/migrations/20261002182226_account_deletion_request_fence.sql
# Run this proof only on an approved disposable database. It supersedes the
# legacy account-deletion-hold.sql check after the protocol upgrade.
psql -v ON_ERROR_STOP=1 "$DISPOSABLE_DATABASE_URL" -f supabase/checks/account-deletion-request-fence.sql
(4) and (5) are prerequisites for billing, not optimizations to schedule later. Apply both before Stripe is switched on, and note that they fail in opposite directions:
Until (4) is applied, api/stripe/checkout.js cannot claim its lock and
refuses every checkout as retryable. That is deliberate — the alternative
is creating billable sessions without serialization — and it fails visibly, on
the way in.
Until (5) is applied, api/stripe/webhook.js cannot call
xbar_apply_subscription_event and every entitlement webhook fails. That
one fails on the way OUT, which is the dangerous order: a customer can complete
and pay for a Checkout Session and never have the plan activated, because the
event that would have granted it errors and Stripe eventually stops retrying.
Applying (5) without (4) is therefore the worse half-deployment of the two —
prefer both, and if you must stage them, apply (5) first.
(9) is a prerequisite for the billing-period webhook change, not an
optimization to schedule later. Until it is applied, api/stripe/webhook.js
passes p_billing_period, which matches no function signature, so every
entitlement webhook fails — the same fail-closed direction as (5), but now
the failure is on the way in for the new code rather than the old. Apply (9)
before deploying the webhook change that passes the period.
xbar.reconcile_exclude is how you keep a row the migration would otherwise
downgrade. A populated stripe_subscription_id proves the workspace was billed
through Stripe at some point; it does not prove the current Manual Billing
value came from the old mapper. If you deliberately moved a paying customer to
manual invoicing, or comped them after they had been paying, that row looks
identical from inside the database — list its workspace id here and it is left
alone.
xbar.reconcile_terminal is the other half of that decision. A reconciled row
is written subscriptionRecoverable: true by default, which withholds checkout
so a paused or unpaid subscription cannot be bought a second time. A canceled
subscription will never send another webhook to correct that flag, so a
customer who wants to come back would stay blocked. List the workspaces you have
confirmed in the Stripe dashboard are canceled or expired, and they are written
false and can purchase immediately. The migration's RECOVERABILITY section
carries a query over workspace_subscription_events to shortlist them.
Use plain set, not set local: these are issued before the migration's own
begin, and set local outside a transaction block applies nothing — the
migration would read an empty setting and report reconciliation SKIPPED while
you believed you had confirmed it. Being session-scoped, they outlive the
migration — step 2d above clears all three when you are finished.
After applying, re-run the security advisor and confirm
anon_security_definer_function_executable has dropped to the intended set;
the counts are documented at the bottom of file 3.
GET /api/health returns liveness plus subsystem-configured booleans (no secrets, no database touch). Point uptime monitors here.UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN so limits are shared across all serverless instances; without them the limiter degrades to per-instance in-memory counting.runtime_events through /api/telemetry (rate limited, workspace-verified, capped at 20 reports per session)./site.js, which reports pageviews and CTA clicks to /api/metrics — anonymous (no cookies, no identifiers, honors Do Not Track), CSP-safe, logged in the Vercel function stream, and stored in runtime_events as marketing.* when Supabase is configured.npm run preflight reports which subsystems are configured and what each missing env var keeps switched off; add -- --url <deployment> to probe the live /api/health and compare.stripe_event_id — retried events are acknowledged without re-running the subscription sync.Do not paste supabase/production-schema.sql directly into production. Generate the executable schema first:
npm run supabase:prepare
Then apply supabase/production-schema.generated.sql in the Supabase SQL editor. It converts unsupported policy syntax and appends the idempotent workspace RLS hardening migration.
$12/mo and $120/yr$29/mo and $290/yr$79/mo and $790/yr$199/mo and $1,990/yrSTRIPE_PRICE_ID_* variable in Vercel Preview and Production, plus STRIPE_PRICE_ID_*_ANNUAL for the annual prices (see .env.example for the full list)./api/stripe/webhook for checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted.STRIPE_WEBHOOK_SECRET and verify a test-mode checkout before enabling live mode.Managed checkout is restricted to workspace admins and only returns customers to trusted XBAR origins.
The feature-click upgrade API uses 20261003200000_upgrade_offers.sql for
account-scoped attempts, explicit declines, immutable checkout retries, and a
single account-wide first-period discount claim. This migration is draft:
keep UPGRADE_OFFERS_ENABLED=false until Erin explicitly approves the database
change and the correct Stripe account/configuration is reviewed. Do not apply
it merely because the application or CI is green.
After explicit approval, first rehearse on an approved disposable database:
psql "$STAGING_DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/migrations/20261003200000_upgrade_offers.sql
psql "$STAGING_DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/checks/upgrade-offers.sql
Only after the rehearsal and production approval:
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f supabase/migrations/20261003200000_upgrade_offers.sql
Verify service-role-only RPC/table access before enabling the API. Missing schema/configuration fails closed. No coupon or portal configuration is created by the application. See the billing runbook for the separate Stripe verification gates. Rollback is to disable the feature and restore application code, preserving these additive tables and claims so retries, workspace deletion, or redeployment cannot reset promotion eligibility.
20260911005818_workspace_access_policies.sql — security and workspace access.
Requires the June 5 workspace helpers. Removes recursive membership reads,
restricts membership/invitation management to owner/Admin, and adds atomic
server-authorized invitation acceptance. Deploy the matching client RPC call.
No existing rows are rewritten; direct invitee table writes are now denied.20260924120000_billing_period.sql — schema. Adds billing_period to
workspace_subscription_profiles and replaces xbar_apply_subscription_event
with a 15-argument signature that records it, so an annual purchase is no
longer stored indistinguishably from a monthly one (monthly_rate: 29 with
no period). Additive: one nullable column, one CHECK, one function replace
in a single transaction, no backfill. Must run AFTER step 5, whose function
it replaces; the filename date keeps that order. Not applied by the change
that wrote it — needs the owner's explicit approval (contract rule 15).20260924223000_preserve_trial_in_event_rpc.sql — billing correctness.
Replaces xbar_apply_subscription_event with the same 15-argument
signature, merging payload -> 'trial' from the locked current row into
the incoming profile when the incoming profile carries none. A trial that
starts between the webhook's pre-lock SELECT and the RPC write would
otherwise be erased by the full payload replace, reopening a second free
trial after a later cancellation. Function replace in a single
transaction, no schema change. Must run AFTER step 9, whose function it
replaces; the filename date keeps that order. Not applied by the change
that wrote it — needs the owner's explicit approval (contract rule 15).