fix(skattekonto): remove the drift email, its event and the unused drift route (#2149)
The nightly skattekonto sync emailed "Skattekontot stämmer inte med bokföringen" whenever Skatteverket's saldo differed from BAS 1630 by more than 1 kr, every 24 hours while it lasted. On 2026-09-02 it fired on a 35 842 kr gap that the reconciliation explained to the last krona with 14 unbooked rows, while the Hem notice and the reconciliation page (both gated on unexplained_difference) said nothing was wrong. The check shipped in May 2026 (#525) before any in-app skattekonto view existed; the dashboard tile its comments promise was never built and the drift API route had no consumer. Since 2026-08-25 the reconciliation page and the Hem notice are the surface, with one definition of "stämmer inte". Removed: skattekonto-drift.ts, skattekonto-drift-email.ts, their tests, the skattekonto.drift_detected event type, the handler registration, the cron's drift hook, GET /api/extensions/skatteverket/skattekonto/drift, and the ROPA activity for the mail. The route is dropped from the ungated extension route allowlist to lock the ratchet. skattekonto_drift_tolerance stays: the Hem notice reads it. Stale skattekonto_drift_last_alert_at rows in extension_data are inert. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5.1
parent
5b64df9c80
commit
6e8d76a9cb
@@ -1,34 +0,0 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { ensureInitialized } from '@/lib/init'
|
||||
import { withRouteContext } from '@/lib/api/with-route-context'
|
||||
import { createExtensionContext } from '@/lib/extensions/context-factory'
|
||||
import { computeSkattekontoDrift } from '@/extensions/general/skatteverket/lib/skattekonto-drift'
|
||||
|
||||
ensureInitialized()
|
||||
|
||||
/**
|
||||
* GET /api/extensions/skatteverket/skattekonto/drift
|
||||
*
|
||||
* Returns the current SKV saldo vs GL 1630 drift snapshot for the active
|
||||
* company. Backs the dashboard SkattekontoDriftTile. Returns null when no
|
||||
* snapshot exists yet (fresh company, never synced).
|
||||
*
|
||||
* Access is recorded through the structured logger (Vercel logs; the
|
||||
* observability sink only sees errors and no-ops until a provider is
|
||||
* configured) because the response carries sensitive GL drift figures. Persisting every
|
||||
* dashboard tile poll into event_log would be too noisy: the structured
|
||||
* log line gives an auditable record without overrunning the 30-day event
|
||||
* log retention (SOC 2 CC8.1, ISO 27001 A.8.15).
|
||||
*/
|
||||
export const GET = withRouteContext(
|
||||
'skatteverket.skattekonto.drift',
|
||||
async (_request, { supabase, user, companyId, log, requestId }) => {
|
||||
const ctx = createExtensionContext(supabase, user.id, companyId, 'skatteverket', requestId)
|
||||
|
||||
const drift = await computeSkattekontoDrift(ctx)
|
||||
log.info('skattekonto drift snapshot accessed', {
|
||||
hasDrift: drift !== null,
|
||||
})
|
||||
return NextResponse.json({ data: drift })
|
||||
},
|
||||
)
|
||||
@@ -7,8 +7,6 @@ const mocks = vi.hoisted(() => ({
|
||||
getCompanyIdsWithCapability: vi.fn(),
|
||||
createExtensionContext: vi.fn(),
|
||||
syncSkattekonto: vi.fn(),
|
||||
computeSkattekontoDrift: vi.fn(),
|
||||
maybeAlertDrift: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@supabase/supabase-js', () => ({
|
||||
@@ -34,11 +32,6 @@ vi.mock('@/extensions/general/skatteverket/lib/skattekonto-sync', () => ({
|
||||
syncSkattekonto: (...args: unknown[]) => mocks.syncSkattekonto(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/extensions/general/skatteverket/lib/skattekonto-drift', () => ({
|
||||
computeSkattekontoDrift: (...args: unknown[]) => mocks.computeSkattekontoDrift(...args),
|
||||
maybeAlertDrift: (...args: unknown[]) => mocks.maybeAlertDrift(...args),
|
||||
}))
|
||||
|
||||
vi.mock('@/extensions/general/skatteverket/lib/api-client', () => {
|
||||
class SkatteverketAuthError extends Error {
|
||||
constructor(
|
||||
@@ -120,7 +113,6 @@ describe('GET /api/extensions/skatteverket/skattekonto/sync/cron', () => {
|
||||
(supabase: unknown, userId: string, companyId: string) => ({ supabase, userId, companyId }),
|
||||
)
|
||||
mocks.syncSkattekonto.mockResolvedValue({ booked: 0, upcoming: 0 })
|
||||
mocks.computeSkattekontoDrift.mockResolvedValue(null)
|
||||
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
|
||||
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
@@ -7,7 +7,6 @@ import { orderByStalestSync } from '@/lib/skatteverket/sync-order'
|
||||
import { CAPABILITY } from '@/lib/entitlements/keys'
|
||||
import { createExtensionContext } from '@/lib/extensions/context-factory'
|
||||
import { syncSkattekonto, SKATTEKONTO_LAST_SYNCED_AT_KEY } from '@/extensions/general/skatteverket/lib/skattekonto-sync'
|
||||
import { computeSkattekontoDrift, maybeAlertDrift } from '@/extensions/general/skatteverket/lib/skattekonto-drift'
|
||||
import { SkatteverketAuthError, type SkvAuth } from '@/extensions/general/skatteverket/lib/api-client'
|
||||
import { SkatteverketSkattekontoError } from '@/extensions/general/skatteverket/lib/skattekonto-client'
|
||||
import { markNeedsReconsent, RECONSENT_ERROR_CODES } from '@/extensions/general/skatteverket/lib/token-store'
|
||||
@@ -239,19 +238,6 @@ export async function GET(request: Request) {
|
||||
source === 'system' ? { mode: 'system' } : { mode: 'user', supabase, userId, companyId }
|
||||
const syncResult = await syncSkattekonto(ctx, auth)
|
||||
|
||||
// Drift check: compare the fresh SKV saldo against GL 1630 sum. Emits
|
||||
// `skattekonto.drift_detected` when |drift| > tolerance and not throttled.
|
||||
try {
|
||||
const drift = await computeSkattekontoDrift(ctx)
|
||||
if (drift) await maybeAlertDrift(ctx, drift)
|
||||
} catch (driftErr) {
|
||||
console.error('[skattekonto-sync-cron] Drift check failed', {
|
||||
userId,
|
||||
companyId,
|
||||
message: driftErr instanceof Error ? driftErr.message : String(driftErr),
|
||||
})
|
||||
}
|
||||
|
||||
results.push({
|
||||
userId,
|
||||
companyId,
|
||||
|
||||
Reference in New Issue
Block a user