Skip to content

Dev - #236

Merged
scottcarlton merged 25 commits into
mainfrom
dev
Jul 10, 2026
Merged

Dev#236
scottcarlton merged 25 commits into
mainfrom
dev

Conversation

@scottcarlton

Copy link
Copy Markdown
Owner

No description provided.

scottcarlton and others added 25 commits July 9, 2026 10:50
Adds a `stores` directory table + `store_users` so retailers can sign up
unaided and land on the existing buyer portal. Rejects `org_type='store'`
to avoid a third federation direction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 tasks, TDD. Corrects three spec errors found while reading the
real files: /shop already has its empty state, /dashboard has none
plus two dead links, and there is no automated RLS test harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t loop

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nsight

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reference-identity + local-override + drift-stewardship model. Pin on
edit, warn only the diverging brand on the diverged field, reconcile
wholesale or per-field, surfaced at point-of-use incl. the order.
Substrate (retailers table vs org_type='retailer') left explicitly open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retailers become org_type='retailer' with organization_members; hook
forks the membership branch on org_type so retailer-org members reach
the buyer portal. Supersedes the v1 retailers/retailer_users tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworks the unmerged v1 retailers/retailer_users tables into first-class
organizations. A retailer is now an organizations row (org_type='retailer')
whose founder is an organization_members admin, reaching the buyer portal via
isBuyer resolved from org_type in hooks.server.ts.

- migration: drop retailers/retailer_users + helpers; widen org_type CHECK to
  include 'retailer'; accounts.retailer_id -> accounts.retailer_org_id (SP4 seam)
- createRetailer: insert organizations + organization_members (admin), idempotent
  on an existing retailer-org membership, slug + 409 collision like create-org;
  no self-brand/seasons/shipping seeding
- hooks: fork the membership branch on org_type (retailer -> empty buyer context,
  /dashboard); fork the /login redirect; drop locals.retailer
- buyer-context: query only account_users (retailer handled in the hook)
- auth/callback + onboarding: route retailer-org members to /dashboard
- types: OrgType += 'retailer'; drop Retailer/RetailerUser; app.d.ts drops retailer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd, routing helper

Addresses adversarial review of the SP1 retailer-org rework:

- createRetailer idempotency now filters org_type='retailer' server-side via an
  inner join, so a user who admins both a rep/brand org and a retailer org can't
  fall through and mint a duplicate retailer
- roll back (delete) the just-created org if the membership insert fails —
  otherwise an ownerless org holds the slug forever and 409s every retry
- set onboarding_completed_at atomically in the org INSERT; drop the unchecked
  client-side completion write in the onboarding wizard
- block buyers (retailer-org members AND legacy account_users buyers) from the
  org AI endpoint via `|| locals.isBuyer` in the 401 guard
- move the profiles.display_name write to after successful org+member creation
  so a 409/500 no longer mutates the profile
- retailer hook branch sets brandScope/scopedBrandNames = null explicitly
- extract landingPathForOrgType() helper, used by both the /login fork and
  auth/callback so they can't drift; add landing.test.ts (3 org types)
- migration: DROP CONSTRAINT IF EXISTS for fresh-DB robustness

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend get_buyer_account_ids() — the single choke point every buyer RLS
policy resolves through — to also return accounts linked to a retailer org
the caller belongs to, via accounts.retailer_org_id. One function; every
downstream buyer policy (brands, products, variants, images, accounts,
orders, order_lines, seasons) inherits retailer scope. No new policies.

Security: link is necessary but not sufficient — a retailer sees a brand
only when a linked account ALSO has an account_brand_access grant (the
brand stays in control). Scoped to the caller's own memberships and
org_type='retailer'; legacy account_users buyers untouched (UNION only
adds). Proven end-to-end against real local RLS (11/11): link+grant sees
brand/product, link-without-grant sees neither, cross-tenant sees nothing.

hooks.server.ts retailer branch now resolves buyerAccounts/buyerBrandIds
from linked accounts via new resolveRetailerBuyerContext(), mirroring the
RLS helper in the app layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dev extracted hooks.server.ts context-loading into src/lib/server/auth.ts
(loadUserContext/applyUserContext, a discriminated union of identity kinds).
Re-home the retailer identity into that structure rather than the old inline
hook:

- auth.ts: add a fifth 'retailer' kind — a hybrid that carries org-member
  locals (membership, allMemberships, orgType='retailer') AND buyer locals
  (isBuyer, buyerAccounts, buyerBrandIds). loadUserContext resolves it via
  resolveRetailerBuyerContext (accounts linked by retailer_org_id); the
  branch precedes brand-scope/SSO, which retailers skip.
- hooks.server.ts: take dev's switch orchestration; 'retailer' (like 'buyer')
  falls through with no redirect → lands on /dashboard.
- auth/callback: keep both dev's isSsoSession (gated domain auto-join) and
  SP1's landingPathForOrgType (retailer → /dashboard, rep/brand → /insight).
- auth.test.ts: add loadUserContext + applyUserContext coverage for the
  retailer branch, matching dev's per-branch test pattern.

Verified: 761 tests pass, bun run check 0 errors, retailer-shopping RLS
harness 11/11 against real local DB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…outing

Address PR #235 review:
- Remove resolveBuyerContext + its tests. dev's loadUserContext inlines the
  invited-buyer resolution, so this export was dead in production and its tests
  gave false confidence (they exercised a copy, not the live path). The two
  could drift silently. resolveRetailerBuyerContext (the live SP3 path) stays.
- /login fork: a membership-less legacy account_users buyer went to /insight
  and was bounced to /dashboard by the insight guard (two hops). Now they route
  to /dashboard directly, and a not-yet-onboarded user to /onboarding, in one
  hop — mirroring auth/callback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(retailer): self-signup as first-class organizations
@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadline Ready Ready Preview, Comment Jul 10, 2026 4:33pm

@scottcarlton
scottcarlton merged commit f863bc4 into main Jul 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant