Files
jobtrackingapp/docs/verification/pol-001-free-pro-entitlements.md
T
cesnimda 134aac7bcf
CI and Deploy / test (pull_request) Successful in 5m19s
CI and Deploy / deploy (pull_request) Has been skipped
feat(ai): centralize durable usage
Add a content-free usage ledger with legacy backfill. Reserve Workspace and durable Strategy/CV work before execution so deleted history or duplicate admission cannot reset limits.
2026-08-15 20:03:06 +02:00

8.0 KiB

POL-001 Free/Pro entitlement verification

Date: 2026-08-02

Status: IMPLEMENTED — NOT VERIFIED. The server policy, worker rechecks, Free locked states and automated regressions pass. Real-browser, Stripe lifecycle, MariaDB and production checks remain unavailable.

Canonical policy

  • External plan names are free and pro only.
  • Free retains core non-AI job tracking, deterministic match scoring, manual profile/CV editing, saved drafts, exports and existing AI history, but cannot start AI work.
  • Pro and Admin use AI and Pro CV themes. The persisted Identity role remains Premium, and Stripe:PricePremium remains a compatibility key; neither is exposed as a public plan name.
  • Current database roles are authoritative on every explicit HTTP AI action. A stale role claim cannot preserve access after downgrade.
  • A locked explicit action returns HTTP 403 with { "code": "pro_required", "message": "This AI feature requires Pro." }.
  • Existing 250-call/1,000,000-token Pro ceilings remain because they are defined in the existing implementation roadmap. Free ceilings are zero. A content-free AiUsageRecord ledger is now authoritative for AI Workspace plus durable Strategy and CV work; legacy AiInteraction usage is backfilled. Older synchronous AI actions still need the same admission boundary before the numeric ceilings can be described as universal.

Entry-point inventory

Capability User entry / frontend API or worker execution path Admission and recheck Usage accounting Free behavior
AI Workspace modules Job details → AI Workspace; AiWorkspacePanel POST /api/jobapplications/{jobId}/ai/generateAiWorkspaceServiceISummarizerService Pro policy with live role lookup Ledger reservation before generation; actual estimate finalized on success Generate disabled; existing history/read/delete remain available
Candidate fit Job details Candidate Fit and Strategy Snapshot GET .../{id}/candidate-fit → attachment/correspondence context → multiple summarizer calls Pro policy No complete shared usage row Deterministic match-score remains available; AI narrative locked
Focus plan Job details Focus Plan and Strategy Snapshot Durable strategy.snapshot operation → summarizer Pro admission plus worker recheck Atomic operation-ledger reservation; successful input/output estimate finalized Locked; no synthetic fallback presented as generated
Interview brief Job details Interview Prep GET .../{id}/interview-prep/brief → summarizer Pro policy No complete shared usage row Editable non-AI interview board remains available; generated brief locked
Tailored CV generation Add Job option and job Tailored CV tab POST .../{id}/generate-tailored-cv-draft → shared generation helpers → summarizer Pro policy No complete shared usage row Job creation and manual tailored-draft editing remain available; no operation is started
Application package Job workspace drafts POST .../{id}/generate-application-package → attachment/email context → summarizer Pro policy No complete shared usage row Existing/manual package drafts remain readable and editable
Follow-up draft Job Follow-up tab GET .../{id}/followup-draft → context → summarizer Pro policy No complete shared usage row Manual correspondence data remains available; AI draft is locked
Job summary refresh Job overview POST .../{id}/refresh-aiSummarizeAsync Pro policy No complete shared usage row Existing summary/tags remain visible; refresh locked
Automatic job summary Job create/detail Core POST /jobapplications and GET /{id} optional summarizer calls Live role condition inside core action No complete shared usage row Core request succeeds without calling AI
CV import/parse Career Profile upload/parse/reprocess /profile-cv/upload, /parse, /reprocess → durable cv.process operation Pro policy before admission; queued run rechecks live roles Atomic conservative operation-ledger reservation; no raw CV content Manual profile editing and previous review runs remain available
CV rebuild/improve/rewrite/PDF Career Profile AI buttons /rebuild, /improve, /rewrite-section, /rewrite-preview, /export-pdf Pro policy; queued rebuild/improve recheck live roles CV-run state only AI controls locked; manual profile data remains available
CV Builder writing aid CV Builder AI Tools POST /api/cv/ai/assist → summarizer Pro policy No complete shared usage row AI buttons disabled; CV editing/history remain available
Pro CV themes CV Builder Customize GET /api/cv/themes; create/save validates selected theme Live role lookup in theme catalog checks Not applicable Pro themes identified and unavailable; existing unchanged selection can still be saved
Job enrichment worker No direct UI; disabled by default JobEnrichmentHostedService per owner Live role recheck immediately before summary; deterministic tag detection still runs for Free No complete shared usage row No model call; core tag enrichment remains possible
Admin AI probe Admin system diagnostics /api/admin/system/ai/probe Admin role; Admin maps to Pro Health metric only Not a Free user path
Periodic service probe No user entry summarizer health probe No private/user payload; operational health only Health metric only Not a user AI capability
Attachment storage Add-job/files UI AttachmentsController storage check Central Free/Pro storage entitlement Bytes stored 250 MB Free; 5 GB Pro (existing defined capability)

Automated evidence

  • ProEntitlementAuthorizationTests: Pro/Admin success, stale-claim downgrade failure, stable 403 body, and reflection inventory of all explicit AI actions.
  • BackgroundWorkerTenantTests: Pro owners use fake AI; Free owners never call it.
  • ProfileCvControllerTests: a queued CV run fails with pro_required semantics after downgrade and never reaches the model.
  • AccountPlansTests: Free zero AI, Pro/Admin AI, and only free/pro external names.
  • AI Workspace UI test: Free locked state, disabled generation and upgrade link.
  • AiUsageMeterTests, operation integration, account export/deletion and SQLite compatibility tests cover idempotent reservation, limits, owner isolation, history-independent totals, Strategy finalization, CV conservative reservation and lifecycle handling.
  • Full backend after the ledger migration: 663/663.
  • Full backend: 568/568.
  • Full frontend: 47/47 suites, 157/157 tests.
  • Production frontend build: pass.
  • git diff --check: no whitespace errors; existing line-ending notices only.

Limitations and remaining checks

  • Browser localhost access is denied by the available browser policy, so 375/768/1440, keyboard, themes and actual navigation to the upgrade action are not claimed.
  • Stripe webhook transitions were code-inspected and existing status tests cover active/trialing vs expired states, but no real or mocked end-to-end checkout/webhook cycle ran in this package.
  • MariaDB and production were not changed or tested.
  • PRODUCT-001 removed landing-page prices, the third “Bring your own key” tier, Free AI allowance and “Unlimited AI” claims. Public capability copy now comes from one two-plan catalogue; commercial terms remain in configured Stripe Checkout.
  • The durable ledger now spans AI Workspace, Strategy Snapshot and CV processing, and deleting user-visible AI history no longer erases usage. Candidate Fit, Interview Prep, application-package/follow-up drafting, CV Builder assistance and automatic summary paths remain synchronous and are not yet universally admitted through this ledger; the UI must therefore avoid claiming that the displayed numeric ceiling covers every AI path.

Rollback

Revert the policy registrations, action attributes, worker checks and frontend plan context together. No schema or dependency change is involved. Keep workers disabled during rollback; reverting only the worker rechecks would restore a downgrade bypass.