19cbb0094b
The Dokumentinkorg (invoice-inbox) leaked past the paywall: visible in the sidebar, command palette, and home "Att gora" list, its page directly reachable, and every non-AI HTTP route open. Its whole value is AI field extraction (Claude Sonnet 4.6 via Bedrock), already the paid chokepoint elsewhere, so gate the whole surface on CAPABILITY.ai. - EXTENSION_REQUIRED_CAPABILITY map + resolvers (keys.ts, sectors.ts) as the single source the nav item, the page, and the API dispatcher all read. - Hide the sidebar item, command-palette entry, and home inbox row for non-payers; subtract inbox_document from the "Att gora" total via one shared visibleWorklistTotal helper (KPI tile + header cannot drift), clamped to >= 0. - Block the /e/[sector]/[slug] page (fail-closed) with an upsell EmptyState. - Enforce the capability in the extension API dispatcher (the single chokepoint that already enforces MFA), so every company-context inbox route 403s. The skipAuth /inbound webhook stays open (freeze-and-retain). - FORCE_PAYWALL=true override so the real gate is exercisable in local dev. - Tests: gating resolver, FORCE_PAYWALL, dispatcher 403/allow/webhook-exempt, visibleWorklistTotal, and enable-banking /connect + /sync 403. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
import type { WorklistCounts } from './types'
|
|
|
|
/**
|
|
* The "Att göra" total as the user actually sees it. The raw worklist total
|
|
* counts inbox_document, but the Dokumentinkorg is a paid (AI) surface hidden
|
|
* from non-payers, so its documents must not inflate the count either, else the
|
|
* dashboard tile shows "N att göra" over a section that renders no such row.
|
|
*
|
|
* Single source for the KPI tile (DashboardContent) and the section header
|
|
* (AttGoraSection): both must agree, and a mismatch here was a real bug. `extra`
|
|
* carries dashboard-only additions (expiring bank connections) that are not a
|
|
* lib/worklist category.
|
|
*/
|
|
export function visibleWorklistTotal(params: {
|
|
total: number
|
|
inboxDocumentCount: number
|
|
hasAi: boolean
|
|
extra?: number
|
|
}): number {
|
|
const { total, inboxDocumentCount, hasAi, extra = 0 } = params
|
|
// total already includes inbox_document, so the subtraction is >= 0 in normal
|
|
// operation; clamp anyway so a transient count skew can never render a
|
|
// nonsense negative "N att gora" on the dashboard tile.
|
|
return Math.max(0, total + extra - (hasAi ? 0 : inboxDocumentCount))
|
|
}
|
|
|
|
/** Convenience overload taking the whole counts object. */
|
|
export function visibleWorklistTotalFrom(
|
|
worklist: WorklistCounts,
|
|
hasAi: boolean,
|
|
extra = 0,
|
|
): number {
|
|
return visibleWorklistTotal({
|
|
total: worklist.total,
|
|
inboxDocumentCount: worklist.counts.inbox_document,
|
|
hasAi,
|
|
extra,
|
|
})
|
|
}
|