Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
Guide users through financial document workflows with AI assistance and human review
AI summary · README
FinancePython · TypeScriptGitHub @PrasadBantOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
A deterministic financial-journey recovery platform: guided document/form workflows across six financial products, backed by a local-or-cloud Document AI pipeline, a human review queue, and event-driven notifications.
Status: Hackathon/demo prototype, actively deployed.
Live frontend: https://paytmflow-frontend.vercel.app
Live backend health: https://paytmflow-backend.onrender.com/api/v1/health
API docs: https://paytmflow-backend.onrender.com/docs
License: Not currently specified in this repository.Read the full README ↗A deterministic financial-journey recovery platform: guided document/form workflows across six financial products, backed by a local-or-cloud Document AI pipeline, a human review queue, and event-driven notifications.
Status: Hackathon/demo prototype, actively deployed. Live frontend: https://paytmflow-frontend.vercel.app Live backend health: https://paytmflow-backend.onrender.com/api/v1/health API docs: https://paytmflow-backend.onrender.com/docs License: Not currently specified in this repository.
PaytmFlow is a guided workflow engine for financial products — a personal loan, a health insurance enrollment, periodic KYC re-verification, a co-branded credit card, a savings account, or a mutual fund investment. A user picks one of six journeys, and the system tracks exactly which requirements are still outstanding, explains why each one is blocked, recommends a single next action, and interprets any document the user uploads through a real document-intelligence pipeline before deciding whether that document actually satisfies the requirement.
It solves this by keeping two concerns strictly separate: an AI/OCR layer that interprets evidence (what kind of document is this, what does it say, how confident is that reading), and a deterministic engine, driven entirely by per-journey YAML configuration, that decides whether a requirement is satisfied and writes the resulting state. The AI layer never has write access to journey state — it can only produce a proposal that the deterministic engine independently validates.
When something can't be resolved deterministically — two documents that disagree on an amount, evidence that's plausible but under the confidence threshold — the case is routed to a lightweight Human Review Center rather than guessed at by a model, and outbound events (Slack for reviewers, email for customers) are dispatched through a real n8n Cloud workflow at each meaningful lifecycle point.
It's built for two overlapping audiences: an applicant moving through a financial workflow who needs to know what's missing and why, and a reviewer who needs a queue of exactly the cases that genuinely require a human decision — not every case.
Financial workflows built around document collection tend to fail users in the same three ways:
The common underlying cause is that document interpretation and workflow decision-making are usually the same step, often behind an opaque or fully-manual process, so there's no way to trust a decision or to reproduce it later.
PaytmFlow separates interpretation from decision at the architecture level, not just as a convention:
Lending, Insurance, KYC, Credit Card, Account Opening, and Investment all
share one frontend and one backend engine. Each journey's required fields,
dependency graph, available actions, and evidence mappings live in a YAML
manifest (backend/app/packs/manifests/) — there is no
if journey_type == "LENDING" branching anywhere in the engine or the UI.
Journey state is a set of typed fields (SATISFIED, BLOCKED,
AMBIGUOUS, ...) connected by an explicit dependency graph. Every mutation
must supply the snapshot version it expects; a stale or conflicting
mutation is rejected outright. Snapshots are immutable and append-only,
enforced by database triggers, not just application code.
A local, from-scratch pipeline (PyMuPDF + Tesseract OCR, a TF-IDF/logistic- regression classifier trained per journey, rule-based field extraction with OCR-noise correction) runs with no external API. An optional cloud provider, Sarvam AI, can be swapped in by configuration for Document AI, speech-to-text, translation, and chat — with automatic, logged fallback to the local pipeline on any failure.
Cases the deterministic engine can't resolve on its own are queued, claimable (with optimistic-lock protection against two reviewers grabbing the same case), and resolvable — resolution re-enters the same deterministic mutation path a customer's own action would use, never a separate write path. An optional AI-generated case summary is available as an advisory aid, always labeled as such.
Six real lifecycle events — review required, evidence uploaded, customer
action required, journey resolved, escalated, and journey completed — are
dispatched to a production n8n workflow with header-secret auth and an event
allowlist. Each event is written to a transactional outbox in the same commit
as the change that emits it, then delivered at least once with one stable
event_id.
A signed, anonymous session cookie (itsdangerous; HttpOnly, Secure,
SameSite=Lax) scopes every journey. Cross-session access to another
session's journey returns 404, verified directly against the live
deployment.
User
↓
React Frontend (Vite, deployed on Vercel)
↓ REST / JSON — contract/openapi.yaml
FastAPI Backend (deployed on Render)
↓
Journey Service → Deterministic Engine (pure, no I/O)
↓ ↑
Evidence Pipeline ─────────┘ (proposes; never writes state directly)
├── Local Document AI (OCR + classifier + extraction)
└── Sarvam AI (optional cloud provider; falls back to local on failure)
↓
Ambiguous? → Human Review Center → same deterministic mutation path
↓
PostgreSQL (append-only, trigger-enforced immutable snapshots)
↓
n8n Cloud (Slack for reviewers, email for customers) — fired only after commit
flowchart TD
User["User (Browser)"]
FE["React + TypeScript SPA<br/>(Vercel)"]
API["FastAPI Backend<br/>(Render)"]
JS["Journey Service"]
ENG["Deterministic Engine<br/>(app/core — pure, no I/O)"]
EVID["Evidence Pipeline"]
LOCAL["Local Document AI<br/>PyMuPDF + Tesseract + scikit-learn"]
SARVAM["Sarvam AI<br/>(optional cloud provider)"]
RCS["Review Case Service"]
DB[("PostgreSQL<br/>append-only snapshots")]
N8N["n8n Cloud workflow"]
SLACK["Slack (reviewers)"]
EMAIL["Email (customers)"]
User --> FE
FE -- "REST / JSON" --> API
API --> JS
JS --> ENG
JS --> EVID
EVID --> LOCAL
EVID -. optional .-> SARVAM
SARVAM -. fallback on failure .-> LOCAL
EVID -- proposal only --> ENG
ENG -- ambiguous field --> RCS
RCS -- resolution re-enters --> ENG
ENG --> DB
ENG -- event queued on commit --> N8N
N8N --> SLACK
N8N --> EMAIL
Only the deterministic engine writes to PostgreSQL. The evidence pipeline and the review service can only produce proposals or re-enter the engine's own mutation path — neither has an independent write route to journey state.
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React 18 + TypeScript, Vite | UI framework, dev server/build |
| Frontend | React Router, TanStack Query, Zustand | Routing, server state, local UI state |
| Frontend | React Hook Form + Zod, Tailwind CSS | Forms/validation, styling |
| Frontend | MSW, Vitest + Testing Library, Playwright | Mock API layer, unit tests, e2e tests |
| Backend | Python 3.12+, FastAPI, Pydantic v2 | Runtime, API framework, schema validation |
| Backend | SQLAlchemy 2 (async), Alembic, PostgreSQL 16 | ORM, migrations, persistence |
| Backend | itsdangerous, uv | Signed session cookies, dependency management |
| AI / Document processing | PyMuPDF, Tesseract (pytesseract), Pillow | PDF text extraction, OCR, image preprocessing |
| AI / Document processing | scikit-learn, joblib | Per-journey document classification, model serialization |
| AI / Document processing | Sarvam AI (api.sarvam.ai) | Optional cloud Document AI, speech-to-text, translation, chat |
| Integrations | n8n Cloud | Outbound event notifications (Slack + email) |
| Infrastructure | Render, Vercel | Backend + Postgres hosting, frontend hosting + API proxy |
| Quality | pytest, ruff, mypy, import-linter | Backend testing, linting, typing, architecture-boundary enforcement |
| Quality | ESLint, tsc, pip-audit / npm audit | Frontend linting/typing, dependency vulnerability scanning |
paytmflow/
├── frontend/ React + TypeScript SPA
│ ├── src/screens/ Ten journey-flow screens + Help
│ ├── src/screens/review/ Review Center screens
│ ├── src/components/ Shared UI and primitives
│ ├── src/mocks/ MSW fixtures for offline (mock-mode) development
│ └── tests/ Unit and end-to-end tests
├── backend/ FastAPI service
│ ├── app/core/ Pure deterministic engine (no I/O) — import-linter enforced
│ ├── app/api/ REST endpoints
│ ├── app/services/ Orchestration layer (journeys, review cases)
│ ├── app/evidence/ Evidence resolution, storage, reconciliation
│ ├── app/docai/ OCR, classification, extraction, trained models
│ ├── app/ai/ AIProvider implementations (mock, local_ml, llm, Sarvam) + guardrails
│ ├── app/integrations/ Outbound n8n webhook dispatcher
│ ├── app/security/ Session signing, reviewer-role authorization
│ ├── app/packs/manifests/ Per-journey YAML configuration
│ ├── Dockerfile Production image (non-root user)
│ ├── docker-compose.yml Local Postgres for development
│ └── tests/ Unit, integration, contract, and safety tests
├── contract/
│ └── openapi.yaml Single source of truth for the API surface
└── README.md
git clone <repository-url>
cd paytmflow
| Requirement | Needed for |
|---|---|
| Python 3.12+ | Backend runtime |
| uv | Backend dependency/virtualenv management |
| Node.js (recent LTS) + npm | Frontend tooling |
| PostgreSQL 16 (via Docker, recommended) | Persistence |
| Docker | Local Postgres via backend/docker-compose.yml, and the production Dockerfile |
Tesseract OCR on PATH | Local Document AI pipeline (AI_PROVIDER=local_ml) |
| A Sarvam AI API key (optional) | Only if you set AI_PROVIDER=sarvam |
| An n8n Cloud webhook URL + secret (optional) | Only if you set N8N_ENABLED=true |
Copy backend/.env.example to backend/.env and frontend/.env.example
to frontend/.env. Never commit real secrets. The full, commented list
lives in the .example files; the variables that matter most:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | PostgreSQL connection string |
APP_ENV | Yes | local | ci | any other value (e.g. production) — see Security |
SESSION_SECRET | Yes (outside local/ci) | Signs session cookies; app refuses to start with the placeholder value outside local/ci |
DEMO_RESET_SECRET | Yes (outside local/ci) | Gates the destructive /demo/reset endpoint |
REVIEWER_ACCESS_CODE | Yes (outside local/ci) to use the Review Center | Code required to switch a session into the reviewer role; with it unset outside local/ci, reviewer access is refused. Rotating it revokes existing reviewer sessions |
ALLOW_EVIDENCE_ACTIONS_WITHOUT_EVIDENCE | No | Test-fixture switch; defaults to on only in local/ci. Keep unset in deployed environments so upload steps require a real, verified document |
CORS_ORIGINS | Yes | Comma-separated list of allowed frontend origins |
AI_PROVIDER | Yes | mock | llm | local_ml | sarvam |
SARVAM_ENABLED / SARVAM_API_KEY | No | Enables the real Sarvam AI cloud provider |
N8N_ENABLED / N8N_WEBHOOK_URL / N8N_WEBHOOK_SECRET | No | Enables real outbound event notifications |
N8N_MAX_ATTEMPTS / N8N_RETRY_BACKOFF_SECONDS / N8N_FLUSH_BUDGET_SECONDS | No | HTTP tries per send (default 3, backoff 0.5s then 1s, or Retry-After), within an 8s total budget per request; anything undelivered stays PENDING in n8n_outbox |
N8N_OUTBOX_SWEEP_SECONDS / N8N_OUTBOX_LEASE_SECONDS / N8N_OUTBOX_MAX_ATTEMPTS / |
Use placeholders like DATABASE_URL=your_database_url when sharing your
own configuration — never copy real values from .env, logs, or a
deployment dashboard into documentation or version control.
cd backend
docker compose up -d postgres # starts PostgreSQL 16
uv sync
uv run alembic upgrade head
uv run uvicorn app.main:app --port 8000 --loop none
API: http://localhost:8000 — interactive docs at http://localhost:8000/docs.
cd frontend
npm install
npm run dev
Frontend: http://localhost:5173, proxying /api/v1/* to the backend.
With the default VITE_API_MODE=mock, the frontend runs entirely against
MSW-mocked responses and needs no backend at all.
Run both of the above concurrently, then set VITE_API_MODE=live in
frontend/.env to point the frontend at the real backend and database.
You don't need a real bank statement, salary slip, or ID to test the actual product. The flagship Personal Loan (LENDING) journey ships with two committed synthetic demo documents so anyone — a judge, a reviewer, you — can walk the real evidence pipeline end to end with fictional data:
frontend/public/demo-documents/ and
backend/tests/integration/test_demo_documents.py, which submits these
same committed files through the real HTTP evidence endpoint and drives
the journey to READY.All provided documents contain synthetic/demo data only — do not upload real financial or identity documents. Every generated file carries a visible "SYNTHETIC DEMO DOCUMENT — NOT A REAL FINANCIAL RECORD" banner.
Full, authoritative surface: contract/openapi.yaml; interactive Swagger
UI at /docs. Verified from the actual routers in backend/app/api/v1/:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/health | Service/DB health check |
| GET | /api/v1/session | Resolve or create the caller's session |
| GET | /api/v1/journey-packs | List available journey types |
| GET | /api/v1/journey-packs/{journey_type} | Journey pack detail |
| GET | /api/v1/journeys | List the caller's journeys |
| POST | /api/v1/journeys | Create a journey |
| GET | /api/v1/journeys/{journey_id} | Journey state |
| GET | /api/v1/journeys/{journey_id}/recommendation | Next recommended action |
| GET | /api/v1/journeys/{journey_id}/diff | Diff between two snapshot versions |
| GET | /api/v1/journeys/{journey_id}/review-status | Whether the journey has an open review case |
| POST | /api/v1/journeys/{journey_id}/evidence | Upload/preview evidence (no state transition) |
| POST | /api/v1/journeys/{journey_id}/actions | The sole state-mutating endpoint |
| POST | /api/v1/journeys/{journey_id}/clarifications | Answer a pending ambiguity |
| POST | /api/v1/journeys/{journey_id}/chat | Grounded assistant chat |
| POST | /api/v1/journeys/{journey_id}/chat/voice | Voice input (Sarvam speech-to-text) → same chat path |
| POST | /api/v1/translate | Translate an explanation/chat string (Sarvam Mayura) |
| POST | /api/v1/review/role | Self-switch the caller's session into the reviewer role |
| GET | /api/v1/review/dashboard | Reviewer-only case queue summary |
| GET | /api/v1/review/cases / |
No screenshots or demo media are committed to this repository. The live, working deployment is the demo — see the links at the top of this document.
| Suite | Command | Result (2026-09-30) |
|---|---|---|
| Backend pytest (PostgreSQL 16) | cd backend && REQUIRE_POSTGRES=1 uv run pytest | 760/760 passed with all non-loopback network access blocked (includes 7 PostgreSQL concurrency tests, which skip on SQLite) |
| Frontend unit tests (Vitest 3) | cd frontend && npm run test | 418/418 passed |
| Frontend E2E (Playwright, MSW mocks) | cd frontend && npm run test:e2e | 52/52 passed (the config pins VITE_API_MODE=mock) |
| Frontend E2E (Playwright, real backend) | cd frontend && npm run test:e2e:live (needs local PostgreSQL from backend/.env) | 9/9 passed. Each test creates its own journeys: creation, status, real document upload, completion, resume/isolation, reviewer claim and resolve; a guard test fails if the mock service worker is active |
| Ruff lint | uv run ruff check app tests | Clean |
| Ruff format | uv run ruff format --check app tests | 7 pre-existing files would be reformatted |
| mypy (whole app) | uv run mypy app | 41 errors, all missing library stubs; 39 in offline benchmark/dataset/training scripts, 2 in runtime modules (docai/classifier.py, docai/ocr.py) |
| mypy strict (engine) | uv run mypy --strict app/core | Clean |
| import-linter | uv run lint-imports | Clean - deterministic engine boundary intact |
| ESLint + TypeScript | npm run lint && npm run typecheck | Clean |
| Frontend production build | npm run build | Clean (chunk-size warning) |
| pip-audit (production dependency set) | uv export --no-dev --no-hashes | uvx pip-audit -r - --no-deps --disable-pip | 0 known vulnerabilities |
| npm audit | npm audit | 6 findings (0 critical, 1 high, 5 moderate), all needing major upgrades of Vite 5 or React Router 6; production-bundle exposure is only react-router (not reachable) |
Documented only where actually implemented and, where noted, verified directly against the live deployment this session:
itsdangerous; HttpOnly,
Secure, SameSite=Lax) — no username/password account system.404 (not 403, to avoid confirming
existence) — verified live.X-Session-Id header bypass restricted to local/ci — any other
APP_ENV ignores it and requires the signed cookie — verified live.403, unknown event or missing required field →
400). That check also reported no duplicate notification for a replayed
event_id; receiver-side deduplication has not been re-verified since and
cannot be verified from this repository./chat, journey chat and
voice, /translate): per-session, per-client-IP and per-instance sliding windows,
429 with Retry-After when exceeded. Counters are in process memory,
so each API instance enforces its own limits.queue_event() adds an
n8n_outbox row to the caller's own transaction, so an event exists
exactly when the change that emits it commits (a rollback leaves none).
Dispatchers claim one row at a time (FOR UPDATE SKIP LOCKED, then a
lease with a random token), send it with no database transaction open,
and record the outcome only while still holding the lease. A request
tries its own events for at most 8 seconds after its commit; whatever is
left is retried by an in-app background sweeper (every
) or
( prints counts per status, applies retention).
Timeouts, connection errors and 408/425/429/5xx are retried with backoff
or the server's (bounded); other 4xx, or 30 HTTP tries in
total (), mark the event for a human.
Once the cause is fixed (payload, secret, URL), an operator runs
: it returns
that one event to with a fresh attempt budget and its
original , sends nothing itself, and refuses ,
, or redacted events. A cancelled or crashed
dispatcher never loses a committed event: it is reclaimed when its lease
expires (a cancelled send releases it at once) and sent with the same
and .This is not a claim of formal certification, a completed third-party audit, or production-grade authentication — see Limitations for the specific reviewer-identity caveat.
Backend: containerized (backend/Dockerfile, non-root user, binds
to the platform-injected $PORT, runs migrations on startup), deployed
on Render.
Frontend: built with Vite, deployed on Vercel; frontend/vercel.json
proxies /api/* to the Render backend so both appear same-origin to the
browser — the reason the SameSite=Lax session cookie works across two
separate hosting providers with no backend changes.
Database: PostgreSQL, provisioned independently of the local
docker-compose.yml development setup.
n8n: a separately hosted n8n Cloud instance that PaytmFlow only ever calls outbound to; it never calls back into PaytmFlow.
Traceability: /api/v1/health reports git_sha (from GIT_SHA, or
Render's RENDER_GIT_COMMIT when unset), and the frontend's
index.html carries <meta name="app-commit"> from Vercel's
VERCEL_GIT_COMMIT_SHA (requires "Enable access to System Environment
Variables" in the Vercel project; otherwise it reads dev). Both values
are forced to a hex commit hash; without metadata a deployed backend or
production build reports unknown, and only local/dev reports dev.
This is a hackathon/demo deployment, not a production financial-services environment.
POST /review/role
requires the shared REVIEWER_ACCESS_CODE outside local/ci, but anyone
holding that code is a reviewer; there is no separate employee/staff
account system or per-reviewer identity. A real deployment would need an
authenticated staff identity and role-management system.RATE_LIMIT_TRUSTED_PROXY_HOPS=0 only the per-session and per-instance
limits apply; and because the Render URL is publicly reachable, a client
that bypasses Vercel can choose its own X-Forwarded-For when hops are
configured. The per-instance ceiling is the limit that cannot be dodged,
at the cost that one abusive client can exhaust it for everyone.event_id. A second Slack/email notification is prevented only by the
workflow claiming the event_id through the receipts endpoint before it
notifies (above); a workflow that skips the claim gets no protection.
Neither side is exactly-once: a crash after claiming loses that one
notification instead of duplicating it.expires_at that no cleanup enforces). Outbox rows are not tied
to journey/session deletion; they follow N8N_FAILURE_RETENTION_DAYS
only. The retention value itself is a decision for the application/data
owner.Current (implemented today):
Future (not implemented, realistic next steps):
# Backend
cd backend && uv run pytest && uv run ruff check . && uv run ruff format --check . && uv run mypy --strict app/core && uv run lint-imports
# Frontend
cd frontend && npm run typecheck && npm run lint && npm run test && npm run build
Not currently specified in this repository.
backend/pyproject.toml and frontend/package.json.N8N_RETRY_AFTER_MAX_SECONDS| No |
Background sweep interval (default 30s, 0 = off), claim lease (60s), HTTP tries across all sends before PERMANENT (30), Retry-After ceiling (1h) |
N8N_FAILURE_RETENTION_DAYS | Yes when N8N_ENABLED=true outside local/ci | Days n8n_outbox rows are kept. No default: an engineering retention control whose production value must be selected and approved by the application/data owner; the API refuses to start without it |
RATE_LIMIT_* | No | Per-minute limits on the AI endpoints: chat 20 and translate 30 per session, 120 per client IP, 600 per instance overall. RATE_LIMIT_TRUSTED_PROXY_HOPS (default 0) controls how the client IP is read from X-Forwarded-For; the per-IP limit applies only to public addresses. RATE_LIMIT_REVIEWER_ROLE_* bound reviewer access-code attempts (5 per session, 20 per IP, 60 overall per minute) |
GIT_SHA | No | Commit reported by /health; falls back to Render's RENDER_GIT_COMMIT. Without metadata it reports dev in local/ci and unknown elsewhere |
EVIDENCE_STORAGE_DIR / EVIDENCE_MAX_BYTES | No | Upload storage path and size limit (default 10 MB) |
VITE_API_MODE (frontend) | Yes | mock (MSW, no backend needed) | live (real backend) |
VITE_API_BASE (frontend) | Yes | API base path, default /api/v1 |
/{case_id}| Reviewer-only case list / detail |
| POST | /api/v1/review/cases/{case_id}/claim | Claim a case |
| POST | /api/v1/review/cases/{case_id}/resolve | Resolve a case (re-enters the deterministic engine) |
| POST | /api/v1/review/cases/{case_id}/request-information | Ask the customer for more evidence |
| POST | /api/v1/review/cases/{case_id}/escalate | Escalate for specialist review |
| GET | /api/v1/review/cases/{case_id}/customer-view | Read-only mirror of what the customer sees |
| GET | /api/v1/review/cases/{case_id}/audit | Case audit trail |
| POST | /api/v1/review/cases/{case_id}/ai-summary | Optional AI-generated case summary |
| POST | /api/v1/demo/reset | Destructive demo-data reset (requires X-Demo-Secret) |
N8N_OUTBOX_SWEEP_SECONDSpython -m app.integrations.n8n_redeliver--status--purgeRetry-AfterN8N_OUTBOX_MAX_ATTEMPTSPERMANENTpython -m app.integrations.n8n_redeliver --requeue <event_id>PERMANENTPENDINGevent_idPENDINGDELIVEREDEXPIREDevent_idIdempotency-KeyDELIVERED (or
EXPIRED) only event, event_id, case_id, journey_id and
timestamp remain — the customer email, session id and message are
removed. Stored error text is stripped of URLs and secret-like values.
With N8N_FAILURE_RETENTION_DAYS set, the sweeper deletes DELIVERED and
EXPIRED rows not updated within that period, and marks older PENDING
or PERMANENT rows EXPIRED (personal fields removed, never sent again)
so they are deleted one period later. The demo reset clears the outbox.n8n_receipts): before sending any
notification the n8n workflow calls POST /api/v1/n8n/receipts/claim
with the event_id, authenticated with the same header secret the backend
sends to n8n. The claim is one INSERT ... ON CONFLICT DO NOTHING RETURNING on a primary key, so of any number of concurrent or repeated
deliveries exactly one wins and notifies; the rest are answered as
duplicates. The winner then calls .../complete (SENT), or
.../release after a clean send failure so the retry can send. A SENT
claim is never released. Chosen semantics: never notify twice; the
cost is that a workflow crash after claiming and before the send finishes
leaves the claim CLAIMED and that one notification unsent. Such claims
are counted as receipts_stale_claims by n8n_redeliver --status for a
human to check - they are never reclaimed automatically, because that
could notify twice. Receipts hold identifiers only and are deleted after
N8N_FAILURE_RETENTION_DAYS.X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy, Cache-Control: no-store).secrets.compare_digest) for the
destructive demo-reset endpoint.backend/data/docai/) — not real
customer documents.