Skip to content

refactor(audit): split enqueue helpers out of auditContextWrappers#235

Merged
therealbrad merged 1 commit intomainfrom
fix/split-auditContextWrappers-along-concern-boundaries
Apr 23, 2026
Merged

refactor(audit): split enqueue helpers out of auditContextWrappers#235
therealbrad merged 1 commit intomainfrom
fix/split-auditContextWrappers-along-concern-boundaries

Conversation

@therealbrad
Copy link
Copy Markdown
Contributor

Description

Splits lib/auditContextWrappers.ts along concern boundaries so BullMQ workers no longer transitively depend on Next.js.

Background. On 2026-04-22 the multitenant-workers deployment crashlooped at startup with Error: Cannot find module 'next/headers'. Root cause: workers/testmoImportWorker.ts imported enqueueWithAuditContext from lib/auditContextWrappers.ts, which had a module-top import { headers } from "next/headers". The workers Docker image intentionally strips Next.js from node_modules to save ~900MB (see Dockerfile deps-workers stage), so the require failed.

Hotfix c804cac lazy-loaded next/headers inside withActionAuditContext. This PR replaces that band-aid with a real split.

Changes

  • New lib/auditContextEnqueue.ts — runtime-agnostic BullMQ helpers:
    • enqueueWithAuditContext (function)
    • ActorContextJobData (type)
    • EnqueueSystemOptions (interface)
    • Zero next/* imports. Safe to load in any Node.js context.
  • lib/auditContextWrappers.ts — now Next.js-only:
    • withAuditContext, withActionAuditContext, enrichFromApiAuth
    • Reverted the hotfix's dynamic await import("next/headers") back to a normal top-level import { headers } from "next/headers".
    • Doc header warns workers / non-Next contexts to use ./auditContextEnqueue.
  • Workers updated to the new module:
    • workers/testmoImportWorker.ts (real imports)
    • workers/copyMoveWorker.ts, workers/syncWorker.ts, workers/forecastWorker.ts (type-only imports)
    • workers/testmoImportWorker.test.ts (both dynamic await import(...) call sites)
  • Also updated (caught by transitive-coupling audit):
    • lib/integrations/services/SyncService.ts — pulled in by syncWorker.ts; keeping the wrapper import here would have re-introduced the coupling.
    • scripts/test-budget-alert.ts, scripts/trigger-forecast-recalc.ts, scripts/trigger-milestone-notifications.ts — run as tsx outside Next.
    • app/api/repository/copy-move/route.ts, app/api/admin/elasticsearch/reindex/route.ts, app/api/imports/testmo/jobs/route.ts, app/api/imports/testmo/jobs/[jobId]/import/route.ts — these had enqueueWithAuditContext alongside a Next HOF in one import block; split into two blocks so the wrapper file no longer has to re-export runtime-agnostic symbols.
  • Tests split:
    • lib/auditContextEnqueue.test.ts (new) — enqueue tests, no next/headers mock. Passing this file without any Next.js mock is the implicit proof that the module loads in a Next-free environment.
    • lib/auditContextWrappers.test.ts — keeps the withAuditContext / withActionAuditContext / enrichFromApiAuth tests and the next/headers mock.

Verification

$ grep -rn "next/" workers/*.ts
(no results for non-test worker files)

$ grep -rn "auditContextWrappers" workers/*.ts
(no results)

Related Issue

Follow-up to hotfix c804cac (workers crashloop 2026-04-22 16:30 UTC).

Type of Change

  • Refactoring (no functional changes)

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • E2E tests
  • Manual testing (verification greps above)

Ran locally:

NODE_OPTIONS='--max-old-space-size=16382' pnpm test run \
  lib/auditContextEnqueue.test.ts lib/auditContextWrappers.test.ts \
  workers/testmoImportWorker.test.ts workers/copyMoveWorker.test.ts \
  workers/syncWorker.test.ts workers/forecastWorker.test.ts
→ 2 test files + 4 worker test files, 81 tests passed

NODE_OPTIONS='--max-old-space-size=16382' pnpm type-check    → clean
pnpm exec eslint <touched files>                             → 0 errors, same 6 pre-existing warnings as main (not introduced by this PR)
pnpm exec prettier --check <touched files>                   → clean

Test Configuration:

  • OS: macOS (Darwin 25.4.0, ARM)
  • Node version: via pnpm (repo-pinned)

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published
  • I have signed the CLA

Additional Notes

This is PR 1 of 3 follow-ups from the 2026-04-22 workers crashloop post-mortem:

  1. This PR — split along concern boundaries (architectural fix).
  2. PR 2 (planned) — CI smoke test that boots the workers image so this class of bug can't ship again.
  3. PR 3 (planned) — SHA-tagged worker images so kubectl rollout undo actually works when a bad image does ship.

Workers crashed at startup after c804cac because auditContextWrappers.ts
pulled in next/headers, which isn't present in the workers Docker image.
The hotfix used a dynamic import as a band-aid; this split addresses the
underlying coupling.

- New lib/auditContextEnqueue.ts: runtime-agnostic BullMQ helpers
  (enqueueWithAuditContext, ActorContextJobData, EnqueueSystemOptions),
  zero next/* imports, safe to load in any Node context.
- auditContextWrappers.ts: now Next.js-only (withAuditContext,
  withActionAuditContext, enrichFromApiAuth) with next/headers back at
  the top level — the dynamic-import workaround is no longer needed.
- Worker files, SyncService, scripts, and mixed-import app routes now
  import enqueue helpers from the new module.
- Enqueue tests moved to auditContextEnqueue.test.ts (no next/headers
  mock) so test suite proves the module loads cleanly in a Next-free
  environment.

Verified: grep -rn "next/" workers/*.ts returns zero hits for non-test
worker files. The workers image can now load auditContextEnqueue.ts
without any transitive Next.js requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@therealbrad therealbrad merged commit d84145b into main Apr 23, 2026
5 checks passed
@therealbrad therealbrad deleted the fix/split-auditContextWrappers-along-concern-boundaries branch April 23, 2026 09:52
@therealbrad
Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 0.22.8 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant