mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
## Problem A policy ran over a file but the owner's **free usage was never consumed**. A policy run executes on a **background virtual thread** (`PolicyEngine.submit` → `asyncExecutor`), and Spring's `SecurityContextHolder` is thread-local — so the worker thread has no identity. When `PolicyExecutor` → `InternalApiClient.post` resolves the tool-call API key via `UserService.getCurrentUsername()`, it finds nothing and falls back to the **`INTERNAL_API_USER`** key. The loopback tool calls then authenticate as that system account, so `PaygChargeInterceptor` attributes the charge to *its* team (or none) — the real owner's free grant is untouched. Folder-watch / scheduled triggers are even further removed (fired from a background watch loop with no request context at all). The charging *mechanism* was fine (AUTOMATION, multipart, `openProcess`); only the **attribution** was wrong. ## Fix Propagate the acting identity onto the worker thread using the **audit-principal MDC key** that `UserService.getCurrentUsername()` already reads as its documented async fallback (the same mechanism used for other async jobs). No new plumbing through the executor. - **`runPolicy`** (stored policies — covers triggers *and* manual `runWith`) → bill the **policy owner**. `Policy.owner` is the username stamped at creation, so `getApiKeyForUser(owner)` resolves it. - **`submit`** (ad-hoc Automate/AI one-offs) → bill the **submitting user**, captured on the request thread (it doesn't survive the hop to the worker otherwise). With the principal set, `InternalApiClient` dispatches each tool call as that user → the interceptor resolves the right team → free grant draws / Stripe meters correctly. ## Tests `PolicyEngineTest`: - `runPolicyDispatchesToolCallsAsTheOwner` — asserts MDC `auditPrincipal` == the policy owner at the moment `InternalApiClient.post` is invoked. - `adHocRunDispatchesToolCallsAsTheSubmittingUser` — asserts it's the submitting user for an ad-hoc run. `:proprietary:test` + `:saas:test` + spotless green; coverage gates met. ## Heads-up (not in this PR) Once attributed, **automatic folder-watch / scheduled runs consume free grant (or bill) per file** — set up once, runs forever. That's automation-is-billable working as intended, but a set-and-forget policy can drain an allowance fast, so it may warrant a per-policy cap or a heads-up in the UI. Flagging for a product decision.