Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ POSTGRES_DB=app

# Server
CORS_ORIGINS=http://localhost:80

# Optional managed local-first demo configuration. Keep secrets server-side.
# DATABASE_URL=postgresql://...
# GOOGLE_AI_STUDIO_API_KEY=
# GOOGLE_AI_STUDIO_MODEL=gemini-2.5-flash
# R2_ACCOUNT_ID=
# R2_ACCESS_KEY_ID=
# R2_SECRET_ACCESS_KEY=
# R2_BUCKET_NAME=
# R2_ENDPOINT=
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ coverage
.cache
tmp
temp
var/

.agents
.cortexkit
context/
docs/
CONTEXT.md
AGENTS.md
graphify*
264 changes: 264 additions & 0 deletions DEMO_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# Trestle demo guide

This guide is the click-by-click walkthrough for a hosted or local Trestle
demonstration. Use only the synthetic PDFs in `examples/invoices/`.

## Before you start

You need:

- A running web app and API at `http://localhost:3001` and `http://localhost:3000`.
- A running worker. The API accepts uploads, but the worker moves cases beyond
`Received`.
- Three seeded accounts with the same password. Create or update them with:

```bash
SEED_USER_PASSWORD='<12+ character password>' pnpm --filter server db:seed
```

- An administrator reset enabled. For local private mode, set
`DEMO_RESET_ENABLED=true` and restart the API. Public mode enables reset by
default.

Open the app at <http://localhost:3001/login>.

## Demo accounts

| Role | Email | Use it for |
| ---------------- | ------------------------------- | ------------------------------------------------------------ |
| AP Specialist | `ap.specialist@trestle.demo` | Upload, review, correct, confirm, reject, and post |
| Finance Approver | `finance.approver@trestle.demo` | Approve, return, or reject policy exceptions |
| Administrator | `administrator@trestle.demo` | Configure policy, inspect reference data, and reset the demo |

The password is intentionally not stored in the repository.

## Seeded reference data

A reset restores this baseline:

- Vendor: `Acme Supplies`
- Purchase Order: `PO-1001`
- Currency: `USD`
- Remaining Purchase Order amount: `USD 1,000.00`
- One Purchase Order line: `Paper`, quantity `100`
- High-value approval threshold: `USD 1,000.00`
- Purchase Order tolerance: `2%`
- Missing Purchase Order requires Finance approval: enabled

Reset between scenarios unless a scenario explicitly says to build on the
previous case. Reset removes cases, documents, findings, decisions, posting
attempts, and quotas; it restores the reference data above.

## Scenario map

| Scenario | File | Main path | Expected outcome |
| ---------------------- | ---------------------------------------- | ---------------------------------------------------------- | --------------------------------------- |
| Clean AP posting | `clean-acme-invoice.pdf` | AP review → confirm → post | `Posted` |
| High-value approval | `acme-clean-invoice-002.pdf` | AP review → Finance approval → post | `Posted` |
| Finance return | `acme-clean-invoice-002.pdf` | AP → Finance return → correction → Finance approval → post | `Posted` |
| Missing Purchase Order | `acme-missing-po-invoice-003.pdf` | AP review → Finance approval, or warning-only variant | `Posted` or `Awaiting Finance Approval` |
| Currency mismatch | `acme-currency-mismatch-invoice-004.pdf` | AP review → Blocking Finding | Reject or correct |
| Business duplicate | `acme-duplicate-invoice.pdf` | AP review → duplicate finding | Reject or correct |
| Exact duplicate upload | Any identical file twice | Intake duplicate check | Existing case link; no new case |

The PDFs and their intended checks are also documented in
[`examples/invoices/README.md`](examples/invoices/README.md).

## Scenario 1: clean AP-only posting

This is the shortest complete workflow.

1. Reset the demo as Administrator.
2. Sign in as AP Specialist.
3. Open **Cases → New case**.
4. Upload `examples/invoices/clean-acme-invoice.pdf` and submit it.
5. Wait for the case to move through `Received` and `Processing` to
`Awaiting AP Review`. Keep the worker running.
6. Review the Source Document, extracted Invoice Draft, evidence, and findings.
7. Resolve any resolvable findings and acknowledge warnings if the page asks
for acknowledgement. Blocking Findings must be corrected or resolved before
confirmation.
8. Click **Confirm draft**. The case should become `Ready to Post`.
9. Click **Post invoice**.
10. Wait for the worker. The case should become `Posted` and show an Accounting
System reference in the timeline.

What this demonstrates: asynchronous processing, evidence-based AP review,
server-confirmed confirmation, explicit posting, and an idempotent accounting
result.

## Scenario 2: high-value Finance approval

The second clean sample is above the default threshold only after lowering the
policy for the demonstration.

1. Reset the demo.
2. Sign in as Administrator and open **Administration**.
3. Set the high-value threshold to `10000` minor units, which is `USD 100.00`.
Leave the other policy settings unchanged and save it.
4. Sign in as AP Specialist.
5. Upload `examples/invoices/acme-clean-invoice-002.pdf`.
6. Wait for `Awaiting AP Review`. Review the evidence and resolve or acknowledge
any findings that the page allows you to handle.
7. Click **Confirm draft**. The case should become
`Awaiting Finance Approval`.
8. Sign in as Finance Approver and open the Finance queue.
9. Open the case and inspect the Source Document, Invoice Draft, findings, and
AP confirmation.
10. Click **Approve**, then confirm the decision.
11. Sign in as AP Specialist, open the case, and click **Post invoice**.
12. Wait for `Posted` and inspect the Accounting System reference.

What this demonstrates: an Approval Required finding routes only after AP
Confirmation, and Finance approval is required before posting.

## Scenario 3: Finance returns a case for correction

Use the same low threshold as Scenario 2.

1. Reset the demo.
2. As Administrator, set the high-value threshold to `10000` minor units and
save the policy.
3. As AP Specialist, upload `acme-clean-invoice-002.pdf` and wait for
`Awaiting AP Review`.
4. Confirm the draft. The case should enter `Awaiting Finance Approval`.
5. As Finance Approver, open the case and choose **Return for correction**.
6. Enter an explanation, such as `Please verify the line description against
the source document.`, and confirm the return.
7. As AP Specialist, reopen the case. It should be `Returned for Correction`.
8. Open the correction form, make a deliberate correction, and save it. For a
safe demonstration, change the line description to the wording visible in
the PDF while leaving the amount, currency, and Purchase Order unchanged.
9. Confirm the new revision. The case should return to
`Awaiting Finance Approval`.
10. As Finance Approver, approve the new revision.
11. As AP Specialist, post the case and verify `Posted`.

What this demonstrates: a Finance decision is tied to a revision, a correction
invalidates the old approval, and the revised case requires a new decision.

## Scenario 4: missing Purchase Order policy

The default policy routes a missing Purchase Order to Finance.

### Approval-required variant

1. Reset the demo and leave **Missing Purchase Order requires approval** enabled.
2. As AP Specialist, upload `acme-missing-po-invoice-003.pdf`.
3. Wait for `Awaiting AP Review` and inspect the `purchase_order_missing`
finding.
4. Confirm the draft. The case should become `Awaiting Finance Approval`.
5. As Finance Approver, inspect and approve the case.
6. As AP Specialist, post it and inspect the timeline.

### Finance rejection variant

Follow steps 1–9 above, then choose **Reject** as Finance Approver, enter a
reason, and confirm the rejection. The case should become `Rejected` and the
reason should remain in the Case Timeline.

### Warning-only variant

1. Reset the demo.
2. As Administrator, disable **Missing Purchase Order requires approval** and
save the policy.
3. Upload the same file as AP Specialist.
4. Acknowledge the warning and confirm the draft. The case should become
`Ready to Post` instead of entering the Finance queue.
5. Post it as AP Specialist.

What this demonstrates: the same deterministic finding can route to Finance or
remain a warning depending on the server-side Approval Policy.

## Scenario 5: currency mismatch and rejection

1. Reset the demo.
2. As AP Specialist, upload `acme-currency-mismatch-invoice-004.pdf`.
3. Wait for `Awaiting AP Review`.
4. Inspect the `purchase_order_currency_mismatch` Blocking Finding. The PDF
uses EUR while `PO-1001` is USD.
5. Because the finding blocks confirmation, choose **Reject case**.
6. Enter a reason and confirm the rejection.
7. Verify that the case becomes `Rejected` and that the reason appears in the
Case Timeline.

This is the recommended path for the supplied fixture. A correction path is
also possible if you deliberately change the draft to match authoritative
source and reference data, then revalidate before confirming.

## Scenario 6: duplicate handling

This scenario covers both duplicate protections.

### Business duplicate

1. Reset the demo.
2. Upload `clean-acme-invoice.pdf` as AP Specialist and let it finish
processing.
3. Upload `acme-duplicate-invoice.pdf`. It has different PDF bytes but uses
the same supplier and invoice number as the clean sample.
4. Open the second case after processing. It should contain a non-overridable
`business_duplicate` Blocking Finding.
5. Reject the duplicate with a reason.

### Exact content duplicate

1. Upload the exact same `acme-duplicate-invoice.pdf` one more time.
2. Intake should stop before creating another case and provide a link to the
existing case.
3. Open the existing case from that link. Its Source Document preview should
remain available.

Do not use the “different bytes” business-duplicate file as proof of exact
content duplication: upload the same file bytes twice for that check.

## Recovery actions

### Extraction failure

If a case reaches `Extraction Failed`:

1. Check that the worker is running.
2. Read the failure message and timeline entry.
3. Fix the provider or document configuration if needed.
4. Click **Retry extraction**. Do not upload the same document again.

### Posting failure

`Posting Failed` is retryable only for temporary Accounting System failures.
Click **Retry posting** after the worker is available. Repeated attempts use the
same idempotency key and should produce one Accounting System reference.

The standard web demo uses the deterministic accounting adapter with normal
success behavior. Temporary and permanent accounting failure modes are covered
by automated integration tests; they are not currently selectable from the
Administrator UI.

### Stale version conflict

If another user changes a case while it is open, a command can return a version
conflict. Reload the case, review the current timeline and findings, then repeat
the permitted action against the latest revision.

## Hosted-demo operator checklist

Before sharing the URL:

- Use a separate database and private document bucket for the demonstration.
- Use synthetic PDFs only.
- Keep provider keys in the API/worker environment, never the web build.
- Use a direct, unpooled Neon connection string. Mastra bootstrap relies on
session-level PostgreSQL advisory locks and can hang through a `-pooler` URL.
- Set the exact production web origin in `CORS_ORIGINS`.
- Use HTTPS so secure session cookies work correctly.
- Set extraction quotas and review the model provider spending limit.
- Set `DEMO_MODE=public` only after the public intake path is ready.
- Confirm the Administrator reset works before sharing the URL.

## Public-mode note

The API's public mode accepts server-known fixture IDs and rejects arbitrary
uploads. The current web intake surface is file-upload based, so use
`DEMO_MODE=private` for the hosted browser walkthrough unless the deployment
includes a fixture-picker client. Do not advertise public mode as a browser
upload flow without that client integration.
Loading
Loading