Skip to content

Adopt durable Framework jobs for receipt scans and email reports - #100

Merged
andrewzolotukhin merged 2 commits into
mainfrom
feat/framework-durable-jobs-adoption
Oct 2, 2026
Merged

andrewzolotukhin merged 2 commits into
mainfrom
feat/framework-durable-jobs-adoption

Conversation

@andrewzolotukhin

@andrewzolotukhin andrewzolotukhin commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Original request

Adopt Framework beta 0.0.0-beta-20261001170911 in Xpenser using the PR workflow. Migrate both receipt scans and recurring email reports to durable scheduling, with up to three scan attempts and duplicate-safe result persistence. Keep the consumer-experience working document local-only.

What changed

  • Pinned every Framework dependency to the published beta and deduplicated the lockfile to preserve one shared schema identity.
  • Added explicit PostgreSQL scheduler/artifact migrations, separate typed job definitions and handlers, DI-backed scan endpoints, bounded workers, and orderly shutdown (30-second drain; 45-second container grace).
  • Replaced the scan Map/listeners/timers with durable acceptance, persisted progress, hashed capability tokens, and three-attempt recovery. Images stay in an application-owned table; scan headers, drafts, and result links commit atomically. Saved results are reused after interruption; successful scans clear staged images.
  • Replaced recurring report timers with a stable hourly schedule, coalescing missed ticks and skipping overlapping runs. Preserved delivery flags, period rules, and delivery-ledger ownership. Qualified ambiguous upsert columns exposed by the new PostgreSQL tests. Interrupted provider sends remain pending instead of being blindly resent.
  • Extended browser and Telegram reconnection budgets, retained existing review/confirmation flows, and added job attempt tracing with no sensitive payload attributes.
  • Added documentation, focused unit/browser tests, and PostgreSQL tests for recovery, competing workers, atomicity, permissions, replay, retention, shutdown, schedule identity, and delivery ownership.

Reasoning

The existing PostgreSQL database supplies durability without another queue service. Framework owns claims, leases, retries, events, and recurrence; Xpenser retains budget authorization, artifact storage, domain progress, and idempotent business writes. Compact job contracts avoid JSON/date and payload-size problems.

Execution is at-least-once: an AI request may repeat and incur another charge if its result was not committed. Separate submissions remain separate jobs. This does not add page-refresh recovery, bot-session persistence, historical email backfill, or exactly-once provider delivery. No production deployment or Framework changes are included.

Blog post

  • /blog/durable-receipt-scanning
  • Primary keyword: reliable receipt scanning
  • Published with this PR, with source PR metadata and a real preview screenshot of a synthetic receipt draft.

Screenshots / preview evidence

  • Preview: https://xpenser-pr-100.cleverbrush.com
  • A real synthetic receipt scan produced one $12.34 draft. Restarted only pr100-api-1; the API returned healthy and the same completed scan/draft was readable afterward. No transaction was confirmed. This demonstrates persisted results; PostgreSQL tests cover interrupted-attempt recovery and stale workers.
  • Rechecked the identical persisted result after the final deployment. /blog, the individual article, and the actual hero image render correctly. Source metadata points to this PR and resolves correctly; the existing blog template does not display source-PR metadata as a visible link.
  • Screenshot shows the real review screen, not a simulated recovery state:

Receipt review in the PR preview

Validation

  • Passed: lint, all-workspace typecheck, 1,941 unit tests; API and Telegram builds.
  • Passed: all 44 PostgreSQL integration tests in both UTC and America/Los_Angeles, including concurrent delivery-ledger ownership and interrupted-provider safety.
  • Full local web build skipped because the local filesystem has only a few hundred MB free. The remote preview web build and deployment passed.
  • Final GitHub run 36923666677 passed all required checks on d3200c9; all 34 deployed browser tests passed, including repeated poll failures and terminal scan errors. Focused blog tests also passed locally after adding final metadata.
  • Manual real scan, preview-only API restart, persisted read on final deployment, blog index/article, and hero rendering passed using agent-browser.
  • SigNoz: successful job.transaction-scan span (~5.24 s); trace-linked status requests; no job, migration, or SQL failures. Final-deployment logs contained only deliberate negative-test warnings (400/401/404, including Auth.js). Error traces additionally include existing optional Brandfetch 400/timeouts, unrelated to job adoption.
  • API HTTP metrics are present (446 request-count increase during the final verification window); the initial window's max HTTP duration was ~2.04 s. SigNoz's histogram percentile query returned an internal 500, so percentile validation was skipped; count/max signals succeeded. Web HTTP/runtime metrics are not instrumented; API/web traces and logs were checked instead.
  • Live recurring email delivery skipped: preview EMAIL_REPORTS_SCHEDULER_ENABLED=0. Schedule/delivery behavior is covered by real PostgreSQL tests. No Telegram container is deployed in this preview; bot reconnect behavior is covered by unit tests and its build.
  • PR-ready Telegram notification sent successfully (notifier HTTP 200).
  • The local-only docs/framework-consumer-experience.md was updated to remove the adopted proposal, preserved in the main checkout, and is intentionally not included in this PR.

Rollout notes

Migration 020 is additive. Let old in-memory scans finish before switching deployments; they cannot be recovered retroactively. New jobs use versioned definitions, and the durable worker must remain available for outstanding jobs. Detailed retention, retry, shutdown, and rollback guidance is in docs/background-jobs.md.

@andrewzolotukhin
andrewzolotukhin temporarily deployed to pr-100 October 1, 2026 20:32 — with GitHub Actions Inactive
@andrewzolotukhin
andrewzolotukhin temporarily deployed to pr-100 October 1, 2026 20:46 — with GitHub Actions Inactive
@andrewzolotukhin
andrewzolotukhin marked this pull request as ready for review October 1, 2026 20:53
@andrewzolotukhin
andrewzolotukhin merged commit 9c55c94 into main Oct 2, 2026
4 checks passed
@andrewzolotukhin
andrewzolotukhin deleted the feat/framework-durable-jobs-adoption branch October 2, 2026 06:59

This branch was previously deployed

1 inactive deployment
pr-100 — d3200c92 Deployed Oct 1, 2026 by andrewzolotukhin via Deploy PR environment #377
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant