Skip to content

fix(dashboard): complete multi-tenant migration and repair broken main - #273

Merged
Lakes41 merged 1 commit into
Adamantine-guild:mainfrom
vreabernardo:fix/multi-tenant-migration-repair
Jul 24, 2026
Merged

fix(dashboard): complete multi-tenant migration and repair broken main#273
Lakes41 merged 1 commit into
Adamantine-guild:mainfrom
vreabernardo:fix/multi-tenant-migration-repair

Conversation

@vreabernardo

Copy link
Copy Markdown
Contributor

Description

Finishes the multi-tenant migration described in docs/multi-tenancy.md and repairs main, which currently does not build: merge 7ba7304 committed conflict markers in 7 files, and PRs #267/#268/#269 left literal `n sequences, a spliced Session interface, a dead duplicate POST block in api/guilds, and an emptied adapters/mock.ts.

Linked Issue

Closes #263

Type of Change

  • 🐛 Bug fix
  • ✨ New feature / enhancement

Changes Made

  • lib/repositories/types.ts + adapters/mock.ts: every member/pass repository method takes guildId as first parameter. Mock implementations rewritten (the file was empty on main): per-guild scoping via a getScoped helper, wallet uniqueness keyed guildId::wallet, optimistic concurrency via version (ConflictError on stale expectedVersion), streamAll for members. Defaults match the spec: passes start draft/currentSupply: 0, members start pending.
  • adapters/durable.ts: same tenanted signatures; DurablePassRepository/DurableMemberRepository stubs added (throw until the SQL backend lands), guild update no longer allows overwriting derived memberCount/passCount.
  • lib/validation/mutations.ts: guildId added to SERVER_OWNED_FIELDS (clients sending it get 400, per spec), .passthrough() removed from the four mutation schemas, invalid_type_error/errorMap converted to the zod v4 error param (the repo pins zod ^4.4.3; the v3 params are gone from the types), flattenZodIssues reads v4 invalid_union.errors.
  • Callers migrated to pass guildId: api/passes, api/members (PATCH now reads expectedVersion), api/admin/reconcile, scripts/reconcile.ts, api/verify import path fixed.
  • lib/auth/session.ts: rebuilt the spliced Session interface (guild-scoped roles/activeGuildId, optional csrfToken for bearer sessions); mock sessions deduplicated.
  • api/guilds/route.ts: removed the dead old POST block left after the merge.
  • packages/env: split dashboardEnvBaseSchema (field-level, used by plain runtime reads) from dashboardEnvSchema (strict, used at startup/live-mode construction). Reading getApiMode() no longer requires live-mode secrets, and validation results are no longer cached.
  • packages/integration-client: restored ActivityChange, SENSITIVE_AUDIT_FIELDS, and the changes field on activity events (dropped by the bad merge; audit-diff tests depend on them).
  • Repaired the `n damage in app/activity, app/members, lib/hooks/useActivityFeed, lib/activity/query, test/activity-query.test.ts, and packages/webhook-utils/test/verify.test.js (the file also had a truncated test and mismatched braces; rebuilt against the pre-Expand webhook-utils signature edge case tests #268 version, keeping the new tests).
  • Tests: ported the repository contract suites into test/repositories/contracts.ts including passRepositoryIsolationContract and memberRepositoryIsolationContract (cross-guild access behaves as missing, wallets unique per guild, updates/deletes can't cross tenants). Updated stale fixtures to the tenanted shapes (guildId, version) and checksummed wallets; live tests now set the env vars they need.

Test Evidence

npx tsx --test per file, full dashboard suite (test/*.test.ts + test/repositories/*.test.ts):

TOTAL pass=578 fail=0

Package suites:

packages/env:                # pass 7  # fail 0
packages/integration-client: # pass 10 # fail 0
packages/webhook-utils:      # pass 32 # fail 0

tsc --noEmit (TypeScript 5.6.3, per repo pins) on apps/dashboard: no errors (in my sandbox the only diagnostic is tailwindcss types missing because I could not run a full pnpm install; the dep is declared).

eslint on the touched trees: 28 errors / 41 warnings, every one pre-existing in code this PR does not touch (session-store.test.ts, reconciliation, DashboardLayout, webhook-utils/src/verify.ts, etc.). For comparison, main currently fails to even parse 7 files, so a like-for-like lint of main is not possible. This branch introduces zero new lint findings.

Accessor audit (requested in #263)

Every read/write of the member and pass repositories now flows through guild-scoped methods:

  • app/api/passes/route.ts — GET/POST/PATCH/DELETE pass getActiveGuildId(request); DELETE uses delete(guildId, id).
  • app/api/members/route.ts — GET/POST/PATCH scoped; PATCH destructures expectedVersion and returns 409 on ConflictError; client-supplied guildId rejected with 400 by the schema.
  • app/api/admin/reconcile/route.ts and scripts/reconcile.ts — iterate guilds, count per guild.
  • lib/repositories/adapters/mock.tsgetScoped map per guild; cross-guild id access returns not-found.
  • app/api/guilds/route.ts — counts derived from the scoped repos.

Isolation regressions live in test/repositories/contracts.ts and run against any adapter via mock-contract.test.ts (63 tests).

Checklist

  • I have read CONTRIBUTING.md
  • This PR is linked to an open issue
  • pnpm typecheck passes with no errors (ran tsc --noEmit directly with the pinned TS 5.6.3; see note above)
  • pnpm lint passes (or issues are pre-existing and documented) — all 28 remaining errors pre-date this PR; none are in new/modified logic
  • pnpm test passes (all existing tests still pass)
  • The dashboard starts and renders correctly (if UI or API code changed) — could not run next dev in this sandbox (no full pnpm install); routes are exercised by the route-level test suite instead
  • No secrets, tokens, or API keys are included in this PR
  • Environment variable changes are reflected in .env.example (no new variables)
  • I have updated documentation if new behaviour was introduced (behaviour matches the existing docs/multi-tenancy.md spec)

Additional Notes

apps/access-api and apps/discord-bot tests were not run here; they need @prisma/client/discord.js which I could not install, and this PR does not touch either app.

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.

Enforce strict per-guild (multi-tenant) data isolation across storage and API routes

2 participants