Skip to content

Integrate all open PRs (#42–#48) into main#49

Merged
Abdulkhalek-1 merged 68 commits into
mainfrom
integration/merge-all-prs
Jul 8, 2026
Merged

Integrate all open PRs (#42–#48) into main#49
Abdulkhalek-1 merged 68 commits into
mainfrom
integration/merge-all-prs

Conversation

@Abdulkhalek-1

Copy link
Copy Markdown
Member

Summary

Consolidates all 7 open PRs (#42#48) into main in one verified integration branch — zero dropped changes, no removed features. All branches forked from the same main tip (209e839f), so they were individually mergeable but conflicted pairwise once one landed. This branch resolves everything once and lands green.

Merging this PR delivers the intent of #42, #43, #44, #45, #46, #47, #48 — please close those as merged.

What's included

PR Area Notes
#42 Dashboard API hardening (sec-api-01..11) rate limits, ReDoS/magic-byte validation, permission-escalation guards
#43 Auth/session/CSRF/CSP (sec-auth-01..09) 24h session TTL, session regeneration, CSP nonces, CSRF double-submit
#44 Bot runtime hardening (sec-bot-01..09) webhook allowlist, template-injection fix, canvas name sanitize
#45 Data layer (sec-data-01..08) AES-GCM token encryption + 3 Prisma migrations, atomic cache swap, log redaction
#46 Infra/CI (sec-infra-01..12) Docker secrets, non-root images, new security-scan CI (see follow-up below)
#47 UI/UX remediation Lucide icons, a11y, code-splitting, full i18n (48 locales)
#48 Variable-field editor autocomplete + highlighting + live Discord preview for {variable} fields (stacked on #47)

Merge & conflict resolution

Merged in dependency order (security PRs → #47#48). The only 3 real conflicts, all resolved preserving both sides' intent:

Four files auto-merged but were semantically verified to compose both PRs' logic: session.ts (encryption + 24h TTL), permissions/routes.ts, actions/persistence.ts, config/src/index.ts.

Companion fixes (required for a working, green result)

Verification

  • pnpm typecheck14/14 tasks
  • pnpm test1053/1053 tests green (systems 270, dashboard 416, bot 367)
  • pnpm install --frozen-lockfile passes
  • ⚠️ Migrations: run pnpm db:migrate + pnpm db:generate; on deploy run fix(security): harden data layer and persistence (sec-data-01..08) #45's encryption backfill (scripts/migrate-encrypt-session-tokens.ts) or token encryption is inert.

Follow-up (tracked, not blocking this merge)

#46 adds .github/workflows/security.yml, currently red on pre-existing issues — remediate in a fast follow-up:

  • Rotate the committed YouTube OAuth refresh token in lavalink/application.yml:17
  • Replace remaining youshallnotpass literals in docker-compose.yml (L32/125) with LAVALINK_SERVER_PASSWORD; allowlist commented .env.example placeholders in .gitleaks.toml; set GITLEAKS_LICENSE
  • pnpm audit high sev: bump/pin esbuild/undici/semver via pnpm.overrides
  • trivy: bump node:22-alpine digest + scoped .trivyignore

🤖 Generated with Claude Code

Abdulkhalek-1 and others added 30 commits April 7, 2026 13:55
Implements 11 security hardening plans for the dashboard REST API:

- sec-api-01: block self-grant and wildcard permission escalation
- sec-api-02: restrict audit log userId filter to self for non-owners
- sec-api-03: validate audit log date range query parameters
- sec-api-04: add per-user rate limits to music/commands/giveaways creation
- sec-api-05: magic-byte validation for welcome background image uploads
- sec-api-06: rate limit + 250ms budget on cron preview endpoint
- sec-api-07: reject unsafe regex (ReDoS) in custom command creation
- sec-api-08: drop permission key from 403 error responses
- sec-api-09: exact-match allowlist for audit action filter
- sec-api-10: add X-Content-Type-Options nosniff to image preview
- sec-api-11: distinguish Prisma P2025 from 500s in actions rule update

Adds safe-regex dependency. Includes 49 new tests across 9 files
covering each hardening. Typecheck clean; no new regressions.
Implements 9 security hardening plans covering OAuth, sessions, CSRF,
and CSP:

- sec-auth-01: hard-fail in production when DASHBOARD_SESSION_SECRET
  is missing or too short
- sec-auth-02: use trusted DASHBOARD_PUBLIC_URL for OAuth callback
  instead of attacker-controllable x-forwarded-host
- sec-auth-03: clear oauth_state cookie immediately after validation
  to prevent replay on failure
- sec-auth-04: regenerate sessions on login (drop existing rows for
  the user) to prevent fixation
- sec-auth-05: re-fetch fresh guild membership every 5 minutes in
  requireGuildAdmin to close stale-cache access window
- sec-auth-06: add CSRF double-submit token enforcement on mutating
  /api routes, issued via GET /auth/csrf
- sec-auth-07: replace style-src 'unsafe-inline' with per-request
  CSP nonces (helmet enableCSPNonces)
- sec-auth-08: tighten oauth_state cookie to SameSite=Strict
- sec-auth-09: reduce session TTL from 7 days to 24 hours

Adds DASHBOARD_PUBLIC_URL config field and new csrf.ts shared module.
Typecheck clean. Dashboard test suite: 14 fail / 298 pass -- net
improvement over baseline (15 failures); no new regressions.

Note: SPA client will need a follow-up change to fetch /auth/csrf and
include X-CSRF-Token on mutating requests.
Implements 9 security hardening plans covering the bot process:

- sec-bot-01: regression tests pinning $queryRaw parameterized safety
- sec-bot-02: webhook headers -> strict allowlist (was denylist)
- sec-bot-03: sanitize usernames/guild name before welcome canvas render
  (strip bidi overrides, zero-width chars, control chars)
- sec-bot-04: restrict action rule names to [a-zA-Z0-9 _-]{1,50}
- sec-bot-05: templateEngine - fix escape lookbehind so escaped user
  content can no longer smuggle core variables into the second pass,
  plus adversarial regression tests
- sec-bot-06: validate emoji against Unicode/custom-emoji form before
  message.react()
- sec-bot-07: classify auto-role assignment failures by Discord API
  error code (50013/10011 warn, others error)
- sec-bot-08: level-up announcements restrict mentions to the leveling
  user only via allowedMentions config
- sec-bot-09: add checkBotPermissions for ManageMessages to /warn

Adds sanitizeDisplayName helper in packages/systems/welcome.
Bot test suite: 349 pass / 16 pre-existing baseline failures (no new
regressions). Systems test suite: 234/234 green.
Implements 8 security hardening plans covering encryption, cache
correctness, logging, and schema integrity:

- sec-data-01: encrypt DashboardSession.accessToken with AES-256-GCM
  (session-scoped key via sha256(dashboardSessionSecret)); adds
  isEncrypted/encryptAccessToken/decryptAccessToken helpers and a
  backfill script for legacy plaintext rows
- sec-data-02: atomic swap of actions rule cache - build the new
  per-event Map locally then swap it in to eliminate the empty-cache
  window during reloadGuild
- sec-data-03: constant-time session id comparison via timingSafeEqual
  with equalized not-found-path work (dummy decrypt)
- sec-data-04: DashboardRoleAssignment FK onDelete -> Restrict plus
  new deleteDashboardRoleWithAudit helper that enumerates, audit-logs
  and unassigns inside a single transaction before deleting the role
- sec-data-05: redactSensitive() in @fluxcore/utils logger strips
  Bearer/Basic auth, Discord webhooks, bot tokens, and sensitive query
  params from all log output
- sec-data-06: migrate DashboardAuditLog.details String -> Json (jsonb
  with ALTER TABLE USING migration); routes no longer stringify
- sec-data-07: safeJsonParse warn-on-failure with rule context so
  corrupted rows surface instead of silently degrading
- sec-data-08: action rate limiter keyed per (guildId, eventType)
  so noisy events can't starve quieter ones

Prisma migrations: 3 new (encryption annotation, FK restrict, jsonb).
Typecheck clean; dashboard 287 pass / 15 baseline failures (no new
regressions); systems 228/228 unit tests green (integration tests for
sec-data-04/06 require real test DB).
Implements 12 infrastructure hardening plans covering secrets,
containers, CI scanning, and dev environment defaults:

- sec-infra-01: remove hardcoded Lavalink 'youshallnotpass' default;
  fail-fast in production if LAVALINK_PASSWORD unset
- sec-infra-02: migrate production secrets from env_file to Docker
  secrets with *_FILE pattern; adds secret-files.ts resolver
- sec-infra-03: backup service uses .pgpass from Docker secret
  instead of PGPASSWORD env var; backup.sh is fail-closed
- sec-infra-04: Dockerfile dev/test stages now run as node user
- sec-infra-05: parameterize dev postgres password with ${...:-fluxcore}
- sec-infra-06: BOT_SYNC_SECRET production fail-fast (unset or <32)
- sec-infra-07: Vite dev server defaults to 127.0.0.1; opt-in 0.0.0.0
  via VITE_HOST env var
- sec-infra-08: add .github/workflows/security.yml (pnpm audit +
  gitleaks + Trivy for bot/dashboard/filesystem) and .gitleaks.toml
- sec-infra-09: blank LAVALINK_PASSWORD in .env.example + CI guard
  against reintroducing the hardcoded default
- sec-infra-10: parameterize pgAdmin credentials with fail-fast guard
- sec-infra-11: add docker-compose.no-db-port.yml override + CI guard
  against public postgres exposure
- sec-infra-12: broaden .dockerignore and .gitignore to .env.* glob;
  CI guard blocking tracked env files

New files: security.yml workflow, .gitleaks.toml, secret-files.ts,
docker-compose.no-db-port.yml, vite-config.test.ts, 3 config tests,
secrets/.gitkeep.

Typecheck clean. Dashboard 281 pass / 15 baseline failures (no new
regressions). Bot 325 pass / 16 baseline failures (no new regressions).
Rewrite shared Icon.tsx as a Material-Symbol-name to Lucide component map (same public API, strokeWidth 1.5, size default 20, filled fill). Remove the Material Symbols font link from index.html (eliminates ligature-word FOUT) and add font preconnects; delete the now-dead .material-symbols-outlined CSS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anding)

Heading hierarchy: PageHeader title is now h1, sidebar brand demoted to a non-heading. Remove dead controls: topbar notifications button, sidebar docs/support and footer placeholder links (no real URLs); wire the topbar settings gear to the settings route. Demote the hero's tertiary CTA to a text link. Give the mobile sidebar close button a 44x44 target. Hero min-height vh -> dvh. Add an accessible refetch spinner to the overview day toggle. Add a persistent landing header (logo + login).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop header.notifications and sidebar.docs/support (no longer referenced after the dead-control cleanup) from common.json across all 48 languages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lazy-load the 19 guild feature pages via React.lazy, each wrapped in its own Suspense/PageSkeleton inside the layout Outlet so the sidebar stays mounted. Add Vite manualChunks for recharts/react-dom/tanstack/radix/i18n/lucide. Initial entry chunk drops from 2,225 kB to 181 kB (gzip 552 -> 43 kB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
discord-select/discord-multi-select: raise option contrast (text/80 -> full text) and fix RTL chevron (ml-2 -> ms-2). Add Volume2 to the Icon map (volume_up) for voice-channel indicators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nodes get role/aria-label, larger handle hit targets, and Yes/No branch titles on the condition node; canvas region is labeled. ActionFields/NodeDetailPanel: raw <input> -> shadcn Input, label/id association, aria-required + non-color-only required markers, emoji channel labels -> Icon, reduced-motion guard on the detail panel. Remove dead files: RuleForm, ActionRow, VariableHelper, AddActionNode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Icon-only buttons get aria-labels; destructive actions gain ConfirmDialogs; bare empty states use the shared EmptyState; section headings fixed (no h1->h3 skips); form labels associated (htmlFor/id); inline validation near fields; color-not-only cues added. Page-specific: security raw <select> -> shadcn Select + load skeleton; commands -> DiscordMultiSelect + Switch toggle + unsaved-changes guard + fixed mis-namespaced common: i18n keys; settings non-functional notification/danger controls removed (unsupported by API); permissions matrix labeled + audit log -> Table; scheduled browser-timezone default + cron validation; logs category icons + filter aria-labels.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…obile inputs)

Address three findings from the Obsidian Engine design-system audit:

- Discord blue (#5865F2) is 4.19:1 as text on #0e0e10 (fails WCAG AA).
  Document --color-discord as fill-only and add --color-discord-text
  (#8b94ff, 7.14:1 AAA) for on-dark text/icons. The verbatim Discord
  button palette in the reaction-roles preview is intentional brand
  fidelity (style name shown alongside, so not color-only) — annotated.
- Add a Title-LG (2rem) tier between Headline-SM and Display-LG so
  hierarchy holds on dense pages.
- Render form inputs at 16px below the md breakpoint (14px from md up)
  to prevent iOS Safari focus auto-zoom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postgres 18 initializes its data directory at /var/lib/postgresql (not
the legacy /var/lib/postgresql/data), so mount the named volume there to
persist data across `pnpm dev` restarts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…irection

Regenerate pnpm-lock.yaml to record already-declared dependencies:
@playwright/test ^1.49.0 and @radix-ui/react-direction ^1.1.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract remaining hardcoded UI strings (automation/rules, welcome,
overview, permissions, tempvoice, tickets, music, logs) into i18n, and
fix pre-existing i18n gaps found during the sweep:
- missing/broken keys that rendered raw or showed English in every
  locale (confirmDelete dialogs, starboard/suggestions/moderation/
  warnings, common time.weeks/limit/options)
- an incorrect i18next plural (editor.issues.one/.other -> _one/_other)

Repair 53 broken locale JSON files (41 empty, 12 with unescaped quotes)
and bring all 48 languages to full key parity (1477 keys, zero
[NEEDS TRANSLATION] markers). Add real translations for the major
languages (es, fr, de, pt, ru, ja, zh-CN, ar, ur); remaining locales
use English placeholders pending native review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Taller, reliably scrollable list so all 48 languages are reachable,
  with an on-brand scrollbar using design tokens
- Render the language list LTR with <bdi>-isolated native names so RTL
  locales no longer mirror or reorder the parentheses
- Add translate="no" so browser auto-translation stops rewriting the
  native names (e.g. Japanese, Chinese) into English
- Fix the hover state: hover:bg-surface-container-high referenced a
  non-existent token; use surface-hover with a transition and
  keyboard-focus parity

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ings

From an adversarially-verified RTL/icon/focus audit of the dashboard:

- Icon.tsx: stop applying fill="currentColor" to active icons. Lucide
  glyphs are stroke-based outlines; filling them collapsed detailed icons
  (Hand/Shield/Zap) into solid blobs — the "Welcome icon corrupted when
  active" and "icons all filled" reports. Active state now uses a heavier
  stroke (2.25 vs 1.5), per design.md's "outlined, not filled" rule.
- Sidebar.tsx: nav had pe-2 only, so Link items sat flush against the
  ScrollArea's overflow-hidden edge and the :focus-visible ring was clipped
  on the inline-start side. px-2 gives the ring clearance.
- RuleList.tsx: flow-strip arrowheads used physical border-r, so they
  pointed backwards against the auto-reversed chip order in RTL. border-e.
- NodeDetailPanel.tsx: slide-in-from-end-4 is not a real utility and never
  animated; replaced with slide-in-from-right-4 + rtl:slide-in-from-left-4
  so the panel enters from its end-0 dock edge in both directions.
- dialog.tsx: centered modal carries no data-side, so its physical
  slide-in-from-left never flipped; added rtl: right counterparts.
- HeroSection.tsx: decorative glow used physical right-1/4 with no
  translate, so it didn't mirror; end-1/4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard used animate-in / slide-in-from-* / fade-in / zoom-in classes
(Radix dialogs, dropdowns, selects, tooltips, popovers, and the automation
detail panel) but no plugin provided them — Tailwind 4 core ships none of
these, so every enter/exit animation was a silent no-op app-wide.

Add tw-animate-css (the Tailwind-4-native successor to tailwindcss-animate)
and import it in styles.css. Verified in-browser: animate-in now resolves to
the "enter" keyframes, and physical slide utilities stay physical (no RTL
auto-flip), so the explicit rtl: slide variants added in the prior commit
resolve correctly in both directions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail panel overlays the React Flow canvas, which does not mirror
(nodes stay in LTR pixel space). Using logical end-0 correctly mirrored
the panel to the LEFT edge in RTL — but that dropped it on top of the
trigger/start node and read as broken. Pin it to the physical right in
both directions (border-l, slide-in-from-right, no rtl variant) so it
stays consistent with the unmirrored canvas; the panel's content stays
RTL via inherited dir. Also switch the React Flow control panel's inset
from logical me-2 to physical mr-2 to match its bottom-right position.

Verified in-browser: right-0 keeps the panel on the right edge in both
LTR and RTL, where end-0 flipped it to the left in RTL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause of the "empty automation canvas in RTL" report: React Flow
does not support RTL — under dir="rtl" its transform/coordinate math
breaks and nodes render off-screen, leaving a blank canvas.

Force the canvas container (relative flex-1, holding ReactFlow) to
dir="ltr" so the graph renders normally, and set the NodeDetailPanel
root back to dir="rtl" so its content stays Arabic/RTL. The panel is
already pinned to the physical right (prior commit), which is correct
for an overlay on the unmirrored canvas.

Verified live in the authenticated dashboard (Arabic/RTL): the trigger
+ action nodes now render, a Member Join trigger sets correctly, and the
detail panel docks right with RTL content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radix injects a `display:table; min-width:100%` viewport wrapper for
horizontal-scroll measurement. We only scroll vertically, and that table
wrapper sized 16px wider than its viewport and overflowed, pushing its
inline-start padding outside the clip box — the node detail panel showed
0 padding on one side and 16px on the other (most visible in RTL). Force
the wrapper to display:block so it fills the viewport width exactly;
padding is now symmetric. No component uses horizontal ScrollArea.

Verified live: panel content insets are now 16px on both sides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Autocomplete, inline highlighting, searchable variable browser, and a
Discord-style live preview (hybrid data) for all {variable} template fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requireGuildAdmin authorized off the cached OAuth session snapshot, so
admin access revoked on Discord kept working (up to 30 min) — a
privilege-escalation hole. Authorization now derives from a live
bot-token check (guildAuthz.isUserGuildAdmin: owner / Administrator /
Manage Server computed from current guild roles), surfaced through
resolveUserPermissions. Revocation is honored within the ~60s cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a refresh control on the servers page backed by POST
/api/guilds/refresh, which force-refreshes the user's guilds from Discord
and invalidates stale server-side guild/permission caches. The
manageable-guild filter now also includes guilds the user owns (OAuth
`owner` flag), not just Manage Server / Administrator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Abdulkhalek-1 and others added 21 commits July 8, 2026 11:11
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the plain Textarea + static hint in the level-up announcement
field with VariableEditor, VariableBrowser, and DiscordMessagePreview,
giving users inline token insertion and a live Discord-style preview.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Threads workflow eventType down through NodeDetailPanel → ActionFields
and swaps variable-bearing text/textarea inputs for VariableEditor with
per-event autocomplete. StepPanel action fields get an empty variable
list (not event-scoped in the current model).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ation

Thread eventType into StepPanelProps and compute buildAutomationVariables in
StepPanel so step action fields receive the real token set instead of [].
Fix VariableEditor id prop (text + textarea branches) to use sanitised fieldId
so <Label htmlFor> correctly associates with the input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds DiscordMessagePreview below ActionFields in ActionPanel for
sendMessage, sendDM (content preview) and sendEmbed (embed preview),
using already-computed variables and usePreviewContext for live
guild/user resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…highlight

The highlight backdrop and the text field each carried the full field style
(border + bg-surface-lowest + rounded), stacking two bordered boxes into a
doubled/corrupted border. The field also carried both text-text and
text-transparent, so twMerge could not dedupe the custom color and the field's
own text painted over the colored backdrop, hiding variable highlighting.

Move border/background/rounding/focus-ring to the container (focus-within), and
make both inner layers borderless + transparent-bg with identical padding: the
field text is transparent, the backdrop shows the accent/danger-colored tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add variables.* and variables.entries.* to common.json so VariableBrowser
and VariableEditor render human-readable descriptions for all registry tokens
(welcome/leveling/tempvoice top-level tokens + automation/command entries).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 1 added @testing-library/react, @testing-library/user-event, and jsdom to
apps/dashboard devDependencies, but the docker workflow (repo root not
bind-mounted) meant the root lockfile was never regenerated, so
`pnpm install --frozen-lockfile` (CI/clean clone) would fail. Regenerated from
the current manifests. Diff is additive (peer-dep hash re-resolution only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… forwarding

- vitest.config.ts: widen coverage include to *.{ts,tsx} so .tsx components are not silently excluded
- commands.tsx: gate DiscordMessagePreview content/embed by formResponseType to prevent both rendering at once
- VariableEditor: add aria-required prop and forward it to textarea and input elements
- ActionFields: pass aria-required={field.required} to both VariableEditor branches (text + textarea)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	packages/config/tests/index.test.ts
# Conflicts:
#	apps/dashboard/src/server/shared/middleware.ts
#	apps/dashboard/tests/server/shared/middleware.test.ts
…#43 sec-auth-06)

#43 enforces CSRF double-submit on mutating /api routes but no PR updated the
SPA client, which would 403 every dashboard mutation. apiFetch now fetches/reads
the csrf_token cookie and sends it as x-csrf-token on non-safe methods; the one
raw-fetch mutation (welcome image preview) does the same via getCsrfToken().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-regex from #42)

The merged lockfile came from #47/#48 and never saw #42's apps/dashboard
dependency on safe-regex + @types/safe-regex. Regenerated the lockfile inside
Docker (lockfile-only); frozen-lockfile check now passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update tests to match the merged PRs' intentional behavior changes (none were
caused by conflict resolution). Fixes across 3 packages:

- config/logger: set LAVALINK_PASSWORD (now unconditionally required, #46)
- session: 24h TTL + guildsRefreshedAt mock field (#43); auth: pass callbackUrl
  to getAuthorizationUrl/exchangeCode + mock buildCallbackUrl (#43)
- permissions: add isGuildAdmin to resolveUserPermissions mocks (#43 live-authz);
  actions: extend persistence mock (getLastFiredByGuild etc); anti-raid/starboard:
  additionalProperties strips -> assert 200 + field not forwarded
- pre-existing (identical to main, unrelated to merge): starboard vi.mock hoisting
  + Collection.find mock; Discord.js Collection .map mocks (messageDelete,
  transcript, voiceStateUpdate member.user.bot); lockdown test isolation;
  kick/timeout moderation-persistence mocks

Result: typecheck 14/14, tests 1053/1053 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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