Files
jobtrackingapp/docs/verification/bg-001-tenant-workers.md
T

3.3 KiB

BG-001 tenant-safe worker foundation verification

Updated: 2026-08-02

Status: IMPLEMENTED — NOT VERIFIED. The owner-scoping foundation and default-off activation contract pass local tests. Browser, production canary, durable notification/idempotency and multi-replica gates remain.

Revalidated root cause and scope

Rules, follow-up reminders, daily export and job enrichment created request-scoped JobTrackerContext instances without an HTTP user. Deny-on-null global filters therefore returned no owned rows. Rules swallowed every exception; the other loops appeared healthy while doing empty work. CV processing already uses explicit owner predicates on each unfiltered query and was not changed; backup and AI health probe are tenant-neutral.

BackgroundTenantRunner now performs the sole worker bypass: it enumerates distinct non-empty job owners with IgnoreQueryFilters, opens a new dependency-injection scope per owner, sets CurrentUserService, and then executes all work through the normal tenant filters. It processes owners sequentially, isolates failures, and logs only worker/failure categories and aggregate counts. It refuses to override any HTTP context.

The four repaired workers are deny-by-default through new switches. Old email/export settings alone cannot activate them. Real email, external AI and production services were not called.

Automated evidence

Check Result
`dotnet test ... --filter "FullyQualifiedName~BackgroundWorkerTenantTests FullyQualifiedName~CurrentUserIdLiveEvaluationTests
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --no-restore PASS — 532/532 after the final trust-boundary test
docker compose config --quiet PASS; only unset optional/local environment warnings
git diff --check PASS — no whitespace errors; repository line-ending notices only

Real SQLite tests prove two-owner/no-HTTP filtering, owner-failure isolation, per-owner rules with an idempotent second pass, atomic per-owner daily exports with hashed filenames, fake-AI enrichment for both owners, fake-email reminders for both confirmed owners, default-off behavior for all four workers and HTTP-context override refusal.

Runtime evidence

An isolated app using disposable data under docs/audits/evidence/bg-001-runtime listened on 127.0.0.1:5306. Health returned 200, no daily-export directory was created with all four default-off switches, and the exact JobTrackerApi PID 44980 was stopped; the port and process were then confirmed closed.

Remaining gates and rollback

  • Reminder delivery is not exactly-once across an email-success/database-failure boundary. Keep it off until OPS-001 supplies a persistent notification/outbox operation.
  • AI enrichment must remain off until POL-001/POL-002 and durable AI operations are enforced server-side.
  • Rules and export remain off pending notification/audit and retention/operator rollout respectively.
  • No lease, heartbeat, distributed scheduler, restart/clock-boundary suite, browser surface or production canary was added here.
  • Rollback is setting all four worker switches false, then reverting the runner/service changes. Do not delete export files or undo user-visible mutations without a separate reviewed procedure. No schema migration was introduced.