Skip to content

Repository files navigation

Trestle

Human-governed document operations for supplier invoice processing.

Trestle turns invoices into reviewable, auditable cases. AI extracts structured data and Field Evidence; deterministic rules validate financial facts; authorized people retain control over correction, confirmation, approval, rejection, and posting.

Important

Trestle is an operational MVP and demo system. Use synthetic fixtures for public demonstrations. Production documents require private mode, configured storage, managed PostgreSQL, and reviewed deployment secrets.

What Trestle Does

Trestle supports three pre-created demonstration roles:

Role Responsibilities
AP Specialist Submit a Source Document, review and correct the Invoice Draft, resolve findings, confirm the current revision, and initiate posting
Finance Approver Review policy exceptions, approve, return for correction, or reject the current case revision
Administrator Maintain limited Approval Policy, inspect reference data and operations, and reset synthetic demo data

Core workflow:

  1. An AP Specialist submits one PDF, JPEG, or PNG Source Document.
  2. The API stores the document, creates an Invoice Case, and queues processing.
  3. A worker prepares the document, extracts fields and evidence, then applies deterministic validation.
  4. The AP Specialist compares document and draft, corrects fields, resolves findings, and confirms the current version.
  5. Clean cases become Ready to Post. Cases with Approval Required findings enter the shared Finance queue.
  6. A Finance Approver approves, returns, or rejects the current revision.
  7. An AP Specialist explicitly posts an eligible case to the simulated Accounting System.
  8. The Case Timeline retains attempts, corrections, findings, decisions, retries, actors, timestamps, and versions.

Lifecycle

Received -> Processing -> Awaiting AP Review
                        -> Extraction Failed -> Processing | Rejected

Awaiting AP Review -> Ready to Post
                   -> Awaiting Finance Approval
                   -> Processing | Rejected

Awaiting Finance Approval -> Ready to Post
                          -> Returned for Correction
                          -> Rejected

Returned for Correction -> Awaiting Finance Approval
                        -> Ready to Post
                        -> Processing | Rejected

Ready to Post -> Posting -> Posted
                         -> Posting Failed -> Posting | Returned for Correction | Rejected

Posted and Rejected are terminal and immutable. Validation Findings explain whether transitions are allowed; they are not lifecycle states.

Product Principles

  • PostgreSQL is authoritative for financial state, permissions, corrections, decisions, and posting records.
  • AI extracts values, confidence categories, and evidence. It never validates financial rules, selects authoritative records, authorizes, approves, rejects, or posts.
  • Every financial command checks actor authority, lifecycle state, and expected Invoice Case version.
  • Human corrections remain authoritative. Reprocessing proposes a revision instead of overwriting them.
  • Retryable side effects use stable idempotency keys and tolerate at-least-once delivery.
  • Source Documents, invoice values, raw prompts, signed URLs, credentials, and session tokens stay out of ordinary logs and traces.
  • Public demonstrations use synthetic documents and bounded model usage.

MVP Scope

Included:

  • One seeded Organization with AP Specialist, Finance Approver, and Administrator accounts
  • Single-document intake for content-verified PDF, JPEG, and PNG files up to 10 MB and 5 pages
  • Multimodal extraction with strict structured output and Field Evidence
  • Deterministic arithmetic, duplicate, Vendor, Purchase Order, and two-way matching rules
  • AP Confirmation, conditional Finance Approval, correction, rejection, and explicit posting
  • Durable workflow suspension, background jobs, retries, idempotency, and optimistic concurrency
  • Local filesystem and private Cloudflare R2 document-storage adapters
  • Simulated Accounting System with deterministic success and failure modes
  • Versioned synthetic evaluation reports for quality, evidence, latency, usage, and estimated cost

Excluded:

  • Multiple Organizations, registration, invitations, subscriptions, and billing
  • Email ingestion, batch intake, external notifications, and native mobile apps
  • Real ERP integration, payments, bank-detail processing, or currency conversion
  • Vendor or Purchase Order creation, goods receipts, or three-way matching
  • Generic workflow and policy builders, advanced analytics, or autonomous financial actions
  • Confidential production documents or unrestricted public uploads

Implementation Status

Area Current repository Accepted MVP target
Web React SPA with Work Queue, review workbench, Finance decisions, recovery actions, and Administration UI Implemented
API Hono/OpenAPI routes for auth, intake, cases, commands, documents, Finance, administration, and health Implemented
Persistence PostgreSQL/Drizzle aggregate, references, attempts, evidence, findings, decisions, events, quotas, and balances Implemented
Background work Graphile Worker with separate API/worker processes, bounded attempts, replay-safe handlers, and cron maintenance Implemented
Workflow Mastra PostgreSQL snapshots coordinated against authoritative PostgreSQL state Implemented
AI OpenRouter vision adapter with strict validation, timeout, fallback, and sanitized usage metadata Implemented; opt-in by environment
Documents Private local filesystem or S3-compatible R2 adapter with authorized access and 10 MB/5-page limits Implemented
Accounting Idempotent simulated Accounting System with temporary and permanent failure modes Implemented
Auth Seeded accounts, opaque sessions, role permissions, origin checks, secure cookies, and bounded login throttling Implemented
Tests Domain, PostgreSQL, workflow, authorization, worker, frontend, evaluation, and redaction coverage Implemented
Deployment Development and production-shaped Compose stacks with separate API/worker services and persistent documents Implemented; managed DB/R2 wiring remains deployment-specific

Warning

Do not put production credentials or confidential documents in the public demo. Public mode accepts only server-known synthetic fixtures and applies account/IP quotas.

Architecture

Current runtime:

Browser SPA -> Hono API -> Repository port -> Drizzle -> PostgreSQL

Accepted target keeps one modular server codebase with separate API and worker processes:

flowchart LR
  Web[React web app] --> API[Hono API]
  API --> DB[(PostgreSQL)]
  API --> Documents[Private document store]
  Worker[Node worker] --> DB
  Worker --> Documents
  Worker --> Model[Extraction provider]
  Worker --> Accounting[Simulated Accounting System]
Loading

API and worker may scale independently, but both call the same application operations and adapters. PostgreSQL owns business state; workflow snapshots own recoverable execution checkpoints.

Technology

Current Stack

Layer Technology
Web React 19, TypeScript 5.9, Vite 8
Routing and server state TanStack Router, TanStack Query
Browser transport and state Ky, Zustand, Immer
UI Tailwind CSS 4, shadcn, Base UI, Phosphor Icons
Localization i18next with English and Indonesian resources
API Hono 4, Zod, @hono/zod-openapi, Scalar
Data PostgreSQL 16, Drizzle ORM, node-postgres
Logging and middleware Pino, CORS, secure headers, request IDs, rate limiting
Tests Vitest, Testing Library, MSW, Hono testClient
Tooling pnpm workspaces, oxlint, oxfmt, Husky, lint-staged

Document preparation uses pdfjs-dist, @napi-rs/canvas, and sharp; R2 uses the AWS S3 client; workflows use Mastra PostgreSQL and jobs use Graphile Worker.

Repository Layout

trestle/
├── apps/
│   ├── server/                 # Hono API, Drizzle, repositories, tests
│   └── web/                    # React SPA, routes, features, UI, tests
├── config/
│   └── typescript/             # Shared TypeScript configuration
├── packages/                   # Reserved workspace area; currently empty
├── docker-compose.yml          # Development stack
├── docker-compose.prod.yml     # Production-shaped template
└── pnpm-workspace.yaml

Prerequisites

  • Node.js 22 or newer
  • pnpm 10
  • Docker with Docker Compose for the recommended setup

Getting Started

Docker

cp .env.example .env
pnpm dev

Docker Compose starts PostgreSQL, API, worker, and web services. API startup applies Drizzle, Graphile Worker, and controlled Mastra bootstrap; the worker processes queued jobs separately. Documents persist in the document_data volume. It serves:

Seed demo accounts with a server-side password before using the application:

SEED_USER_PASSWORD='local-only-password' pnpm --filter server db:seed

Stop the stack without deleting database data:

docker compose down

Delete the local PostgreSQL volume only when discarded data is acceptable:

docker compose down -v

Local Processes

Start PostgreSQL, then configure each application:

cp apps/server/.env.example apps/server/.env
cp apps/web/.env.example apps/web/.env
pnpm install
pnpm dev:local

dev:local starts web and server processes in parallel. Server predev applies Drizzle, Graphile, and Mastra bootstrap steps.

Environment

Root .env configures Docker Compose PostgreSQL values:

Variable Required Purpose
POSTGRES_USER Yes Development PostgreSQL user
POSTGRES_PASSWORD Yes Development PostgreSQL password
POSTGRES_DB Yes Development database name
CORS_ORIGINS Production Compose Allowed web origin

apps/server/.env configures a direct server process:

Variable Default Purpose
NODE_ENV production development, production, or test
HOST localhost Bind hostname
PORT 3000 API port
DATABASE_HOST localhost PostgreSQL host
DATABASE_PORT 5432 PostgreSQL port
DATABASE_USER Required PostgreSQL user
DATABASE_PASSWORD Required PostgreSQL password
DATABASE_DB postgres Database name
DATABASE_POOL_MAX 10 Maximum pool connections
CORS_ORIGINS http://localhost:3001 Comma-separated allowed origins
RATE_LIMIT_WINDOW_MS 60000 Rate-limit window in milliseconds
RATE_LIMIT_MAX 50 Requests allowed per window
BODY_SIZE_LIMIT 102400 Request body limit in bytes
DOCUMENT_STORAGE_ROOT ./var/documents Private local document storage
DEMO_MODE private public restricts intake to fixtures
DEMO_EXTRACTION_QUOTA_PER_ACCOUNT 20 Account quota per period
DEMO_EXTRACTION_QUOTA_PER_IP 5 HMAC-hashed IP quota per period
DEMO_QUOTA_PERIOD_HOURS 24 Quota window
OPENROUTER_API_KEY Empty Enables real extraction when configured
R2_* Empty Enables private S3-compatible storage

apps/web/.env contains one public value:

Variable Purpose
VITE_API_URL Hono API base URL, such as http://localhost:3000

Only VITE_ variables may enter the browser bundle. Never place secrets in web environment variables.

Commands

# Development
pnpm dev                    # Docker Compose stack
pnpm dev:local              # Direct web and server processes
pnpm dev:web
pnpm dev:server

# Verification
pnpm lint
pnpm typecheck
pnpm test -- --run
pnpm build

# Focused tests
pnpm test:web -- --run
pnpm test:server -- --run

# Database
pnpm --filter server db:generate  # Generate migration from schema changes
pnpm db:migrate                   # Apply committed development migrations
pnpm db:migrate:test              # Apply migrations to test database
pnpm db:push                      # Disposable local exploration only
pnpm db:studio

# UI primitives
pnpm --filter web shadcn:add <component>

Review generated migration SQL before applying or committing it. Do not rewrite migrations that another environment may have applied.

Testing

  • Web tests use jsdom, Testing Library, userEvent, and MSW at the network boundary.
  • renderPage() creates an isolated memory router and Query Client for route behavior.
  • Server route tests use Hono testClient with fresh in-memory repository adapters.
  • Server setup still requires a disposable PostgreSQL test database for migrations and transaction infrastructure.
  • In-memory adapters do not prove PostgreSQL constraints, locking, transactionality, or SQL behavior.
  • Playwright and complete Trestle end-to-end scenarios are planned but not installed.

The repository currently has no tracked apps/server/.env.test.example. Create apps/server/.env.test from the server environment shape and point it at a disposable test database before running server tests. Do not reuse production data.

Full quality gate:

pnpm lint
pnpm typecheck
pnpm test -- --run
pnpm build

API

Important routes:

  • GET /health
  • POST /auth/login, POST /auth/logout, GET /auth/session
  • POST /invoice-cases/intake
  • GET /invoice-cases, GET /invoice-cases/{id}, timeline and private document access
  • AP commands: confirm, correct, resolve, reprocess, reject, post
  • Finance command: POST /invoice-cases/{id}/finance-decision
  • Administration: policy, Vendors, Purchase Orders, usage summary, and demo reset
  • GET /health, GET /doc, GET /reference

All public routes validate through Zod/OpenAPI schemas. Commands require authenticated actors, explicit role permissions, lifecycle checks, and expected versions. Browser transport uses Ky with normalized error envelopes and TanStack Query invalidation.

Evaluation

The offline evaluation harness lives under apps/server/src/evals/. It scores versioned synthetic manifests without network calls:

pnpm --filter server test -- --run scorer

The opt-in live command requires OPENROUTER_API_KEY and reports harness, schema, prompt, model, token, and score metadata:

pnpm --filter server exec tsx scripts/eval-live.ts

Live extraction is never part of the CI quality gate.

UI Direction

Trestle uses a light, restrained casework interface designed for long document-review sessions. Source Documents, financial values, findings, and human decisions outrank decoration. The target workbench places the document beside the editable draft on desktop and switches between Document and Draft panels on small screens.

UI work follows semantic OKLCH tokens, persistent labels, keyboard access, explicit status text, 44 pixel touch targets, reduced-motion support, and no color-only meaning.

Development Guidance

Key implementation rules:

  • Keep routes thin and delegate through explicit ports.
  • Keep browser server state in TanStack Query and shareable navigation state in TanStack Router.
  • Validate external input, environment values, uploads, model output, and integration responses.
  • Store money as integer minor units with ISO currency.
  • Mock at network or port boundaries and use PostgreSQL integration tests where database semantics matter.
  • Update context or ADRs when implementation changes durable boundaries or invariants.

Recovery And Operations

  • Processing failures become extraction_failed; authorized AP users can retry through the existing reprocess command.
  • Temporary posting failures retry with the same idempotency key. Permanent Accounting rejection is inspectable and cannot be blindly retried.
  • Workflow snapshots are resumable; missing or invalid snapshots become inspectable processing failures.
  • Background jobs have bounded attempts. Duplicate processing and posting jobs are replay-safe.
  • Structured logs carry correlation, case, workflow, job, and attempt identifiers while redacting document content, invoice values, prompts, credentials, signed URLs, and provider output.

Deployment

docker-compose.prod.yml builds one server image into separate API and worker services, runs health-gated startup, and persists local documents. Hosted deployments should replace local PostgreSQL with managed PostgreSQL and configure private R2 through R2_* variables.

The web image requires VITE_API_URL at build time because Vite embeds it in the static bundle:

docker build \
  -f apps/server/Dockerfile.prod \
  -t trestle-server:latest \
  .

docker build \
  -f apps/web/Dockerfile.prod \
  --build-arg VITE_API_URL=https://api.example.com \
  -t trestle-web:latest \
  .

Review CORS origins, production cookie settings, database backups, R2 least-privilege credentials, and the public/private demo mode before deployment. Never expose OPENROUTER_API_KEY, database credentials, R2 secrets, or session material to the web bundle.

About

Human-governed document operations for supplier invoice processing

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages