Technical Debt
Known debt items with context and remediation plans
Not all debt is bad — some is deliberate trade-offs. This register tracks both.
P0 — Production Blockers
2026-04-28 — FILE DOES NOT EXIST
src/mocks/validation.tswas removed. Production usesenrichV7()with real lead scoring (lead_score >= 5). The 4 layer fields inleadstable exist for backward compatibility but don’t drive validation. Real validation layer files (src/validation/layers/) exist but are unimported.
TD-001: Legacy 4-Layer Validation Fields
Files: src/validationEngine.ts, src/validation/layers/*
Debt: 4-layer validation fields (layer_registry, layer_website, layer_profiles, layer_news) populated for backward compatibility but don’t drive is_validated decision. Actual validation uses lead_score >= 5.
Why it exists: Transition from v4 validation engine to v7 enrichment pipeline. Legacy fields kept for API compatibility.
Impact: Low — fields are accurate but unused for validation logic.
Remediation: Either wire up real layer checks or deprecate the fields.
Effort: 1-2 days
Owner: Unassigned
TD-002: Opt-Out Never Checked in Production
File: src/scraping/middleware.ts (definition only)
Debt: isOptedOut() exists but is never called in any API endpoint or worker.
Why it exists: Implemented as middleware but not integrated into request pipeline.
Impact: Critical — GDPR violation. Opted-out individuals still processed.
Remediation: Add middleware-level opt-out gate on ALL data access endpoints.
Effort: 1-2 days
Owner: Unassigned
TD-003: Article 14 Notifications Never Sent
File: src/lib/article14Notification.ts:259-265
Debt: SMTP not configured. All notifications logged as “skipped”.
Why it exists: Email infrastructure not set up. Relies on EMAIL_API_URL env var.
Impact: Critical — Bisnode-level GDPR violation risk.
Remediation: Configure SendGrid/Mailgun/Postmark. Add fallback delivery mechanism.
Effort: 2-3 days
Owner: Unassigned
P1 — High Priority
TD-004: Playwright Inline in Workers
File: src/queues/workers.ts, src/extractors/websiteExtractor.ts
Debt: Playwright runs synchronously inside BullMQ worker threads.
Why it exists: Simplest implementation. Decoupling adds complexity.
Impact: High — OOM/deadlock at >40 concurrent jobs. Scaling bottleneck.
Remediation: Extract Playwright to dedicated microservice with its own queue.
Effort: 1-2 weeks
Owner: Unassigned
TD-005: No Automated Data Retention
Files: All PII tables
Debt: No retention_expires_at columns. No cleanup jobs.
Why it exists: Focus on enrichment pipeline. Compliance deferred.
Impact: High — GDPR Art. 5(1)(e) storage limitation violation.
Remediation: Add retention fields + scheduled cleanup jobs. KB recommends 12-24 months for contact data.
Effort: 3-5 days
Owner: Unassigned
TD-006: pg.Pool and ioredis Still Used
Files: src/db/index.ts, src/lib/redis.ts, src/cache.ts, src/api/middleware/rate-limit.ts
Debt: Project convention requires Bun.sql and Bun.redis, but legacy connections still exist. Multiple pool configurations (6+ pools with same credentials).
Why it exists: Migration incomplete. Some code paths depend on pg.Pool features.
Impact: Medium — inconsistent connection handling, multiple pools, no central connection manager.
Remediation: Migrate all database access to Bun.sql. Migrate Redis to Bun.redis. Create central connection manager.
Effort: 1-2 weeks
Owner: Unassigned
TD-007: SCB Data is Synthesized
File: src/fetchers/scb/index.ts
Debt: SCB API returns statistical aggregates, not real companies. orgNr synthesized from dimension codes.
Why it exists: SCB doesn’t expose real company registry. Only statistics.
Impact: Medium — data quality issue for SCB-derived records.
Remediation: Use Bolagsverket as primary source. SCB only for advertising_block flag.
Effort: 2-3 days
Owner: Unassigned
P2 — Medium Priority
TD-008: No Circuit Breaker on External APIs
Files: src/enrichment/sources/*.ts
Debt: External API failures (429s, timeouts) kill entire enrichment job.
Why it exists: Error handling added per-source but no global circuit breaker.
Impact: Medium — single API failure wastes entire enrichment effort.
Remediation: api_circuit_state table exists but not actively used. Wire up circuit breaker logic.
Effort: 3-5 days
Owner: Unassigned
TD-009: Redis Cache Stores PII Unencrypted
File: src/cache.ts
Debt: Enrichment cache (Redis DB 1) stores full contact data including names, emails, phones.
Why it exists: Performance — encryption would add latency.
Impact: Medium — if Redis compromised, PII exposed.
Remediation: Encrypt sensitive fields in cache. Or use Redis ACL to restrict access.
Effort: 2-3 days
Owner: Unassigned
TD-010: HASH_SALT Module-Level Constant
File: src/compliance.ts:14
Debt: HASH_SALT captured at import time. Cannot rotate without restart.
Why it exists: Simplest implementation.
Impact: Low-Medium — salt rotation requires full restart.
Remediation: Read salt at runtime per-hash. Add salt versioning.
Effort: 1 day
Owner: Unassigned
TD-011: No RoPA Logging on SELECT
Files: All GET endpoints Debt: RoPA only logs writes (INSERT/UPDATE/DELETE). No audit trail for data access. Why it exists: Read logging would be extremely verbose. Impact: Medium — cannot demonstrate who accessed what data. Remediation: Add optional read logging for sensitive endpoints. Or log at API gateway level. Effort: 3-5 days Owner: Unassigned
TD-012: Kundkort CSV Export Missing Reklamspärr Filter
File: src/api/kundkort.ts:962-1085
Debt: exportCsv() does not call isScbAdvertisingBlocked().
Why it exists: Oversight during export implementation.
Impact: Medium — blocked companies can be exported.
Remediation: Add reklamspärr check to export function.
Effort: 2 hours
Owner: Unassigned
Deliberate Debt (Won’t Fix)
TD-W01: No Real-Time Notification Delivery
Context: Art. 14 notifications are queued, not real-time. Rationale: Batch delivery is acceptable under Art. 14(3)(a) “within one month”. Real-time would overload SMTP.
TD-W02: FPR 21.4% in Production
Context: Best experiment achieved 2.6% FPR, production config at 21.4%.
Rationale: Production includes timeouts and parked domains in test set. Fixable by batch additions to INVALID_NAME_STANDALONE_WORDS.
TD-W03: No Nordic Expansion Yet
Context: Norway/Denmark registries not integrated. Rationale: Swedish market not yet saturated. Expansion after product-market fit.
TD-W04: Firecrawl Phase 2 Not Done
Context: A/B comparison between Crawlee and Firecrawl incomplete. Rationale: Crawlee meets current quality bar. Firecrawl reserved for future quality push.