Skip to content

GreenbarSystems/Greenbar-clearing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Greenbar ClearingGreenbar Clearing — a local-first bank reconciliation app for mid-market finance teams.

Built for accountants who need cryptographic audit integrity without sending raw transaction data to a SaaS. Reconcile bank ↔ ERP, resolve exceptions, sign off with separation-of-duties enforcement, and export audit-ready reports — all on your machine. An optional FastAPI/PostgreSQL server adds centralized user management, server-enforced RBAC, and a tamper-evident audit ledger that scales to ~1M transactions/month.

Highlights

🔒 SHA-256 hash-chained audit log; tamper detection via periodic chain verification 👥 Maker/checker workflow with monotonic state transitions + SoD enforcement 🏢 Multi-entity, multi-account, period-scoped storage 🔌 Plaid integration for bank data (server-side credentials, never in browser) 📊 Indexed matching engine + Web Worker offload for large reconciliations 🗄️ Partitioned audit ledger with cold-tier archival (JSONL.gz + manifest) and read-replica routing 🖥️ Tauri 2.0 desktop wrapper (Windows/macOS/Linux) — runs offline by default 💾 IndexedDB-backed row store with tiered eviction policy


What you get

  • Formatter wizard — drop .xlsx / .xls / .csv, auto-map columns, normalise dates (DD/MM vs MM/DD auto-detected) and money values (currency symbols, parens-negatives, trailing CR/DR), surface issues before reconciling.
  • Matching rule engine — four-rule pipeline (exact, reference, amount-tolerance, fuzzy description) with score-descending greedy assignment within each pass. Every paired row carries the rule that produced it + a confidence score + the per-rule sub-scores.
  • Resolution drawer — write-off, adjustment, force-match, reclassify, carry-forward, escalate. Every action is reversible from the resolution log.
  • AI resolution assistant — local pattern-learning engine observes your past resolutions, suggests treatments for new exceptions, supports bulk auto-resolve with per-group preview. CSP-safe; nothing leaves the device.
  • Outstanding-items carry-forward — items roll into next month's reconciliation; stable content-based IDs survive re-runs.
  • Anomaly detection — z-score / IQR outliers, duplicates, sign mismatches, weekend posts, volume spikes.
  • Approval workflow — preparer attestation → reviewer sign-off → exportable approval record. Reopening clears attestation so re-submission requires re-attesting.
  • Multi-entity / multi-account — one install can manage multiple legal entities and multiple bank/GL accounts per entity. Every silo is physically isolated.
  • Audit-ready report — 11-sheet XLSX with cover, summary, all transactions, match provenance, exceptions, resolutions, outstanding items, anomalies, workflow history, engine config snapshot. Tamper-evident Report ID.
  • Mapping templates — auto-detect recurring bank/ERP formats so the column mapping step can be skipped.
  • Plaid integration — optional, network-required, opt-in on desktop. Pulls bank transactions via Plaid Link.

Install

Desktop (recommended)

  1. Download the installer for your OS from the latest release.
  2. Run it. The app appears as Greenbar Clearing in your applications list.

Data location after install:

OS Path
Windows %APPDATA%\com.greenbarsystems.clearing\greenbar.db
macOS ~/Library/Application Support/com.greenbarsystems.clearing/greenbar.db
Linux ~/.local/share/com.greenbarsystems.clearing/greenbar.db

A single SQLite file. Back it up by copying that file somewhere safe; restore by copying it back.

Browser (dev / evaluation)

You can run the same UI in a browser without installing anything. ES modules need an HTTP server — file:// won't work for module imports.

python -m http.server 8000
# then visit http://localhost:8000/

In browser mode the data lives in localStorage. There is no sync to a server unless you wire one up (see Storage backends below). Use this for evaluation or single-machine dev only — localStorage is unencrypted and survives only as long as the browser profile.


Architecture — progressive hybrid

The default is local-first. Cloud capabilities are added one at a time, each opt-in, each governed by an explicit data-classification table.

                ┌────────────────────────────────────────────────┐
                │ Greenbar Clearing (desktop)                    │
                │ ┌────────────────────────────────────────────┐ │
                │ │ Webview (OS native — WebKit/WebView2)      │ │
                │ │ All UI, matching engine, AI assistant,     │ │
                │ │ formatter, audit report, …                 │ │
                │ └─────────────┬──────────────────────────────┘ │
                │               │ tauri::invoke (IPC)            │
                │ ┌─────────────▼──────────────────────────────┐ │
                │ │ Rust host (Tauri)                          │ │
                │ │   kv_get / kv_set / kv_remove / kv_list    │ │
                │ │     → SQLite at app_data_dir/greenbar.db   │ │
                │ └────────────────────────────────────────────┘ │
                └────────────────────────────────────────────────┘
                                       │
                                       │ (opt-in only, per data class)
                                       ▼
                ┌────────────────────────────────────────────────┐
                │ Cloud broker  (optional — disabled by default) │
                │   • Workflow state (plaintext JSON today;      │
                │     E2EE planned — Batch 3)                    │
                │   • User registry (email, role, hash)          │
                │   • Entity / account / template catalogues     │
                │   • Aggregated statistics digests              │
                │                                                │
                │   NEVER:                                       │
                │   • Raw transactions                           │
                │   • AI pattern KB raw events                   │
                │   • Plaid raw transaction data                 │
                └────────────────────────────────────────────────┘

Data classification

Every storage key carries one of three privacy labels. The source of truth is src/services/dataClassification.js.

Class Behaviour Examples
LOCAL_ONLY Hard-coded local. No UI toggle. Never leaves the device. Outstanding items, Plaid link cache, AI KB, active scope, session, sync prefs themselves
SYNC_OPT_IN User-toggleable. Default OFF. Plaintext JSON crosses the wire today — review your server-side controls (TLS, at-rest encryption, access logs) before enabling. E2EE wrapping for workflow state is planned (Batch 3). Workflow state, users, entities, accounts, mapping templates
AGGREGATE_OPT_IN Never raw. Only fixed-shape digests (counts, percentages, rule histograms, balance proof totals) computed by a dedicated reducer ever cross the wire. Period-close statistics

What the cloud sees, if you turn everything on

Counts (matched / partial / resolved / unresolved). Match-rule histogram (exact / reference / tolerance / fuzzy). Clearance %. Balance-proof totals. Workflow state in plaintext JSON (preparer/reviewer names, attestation timestamps, free-text notes, audit history) — server operator can read all of it until Batch 3 lands the E2EE wrapping. User emails + roles + PBKDF2 password hashes + salts. Entity / account / template names + tax IDs + masked-to-last-4 account numbers.

What the cloud never sees, even with everything on

Raw transactions. Descriptions. References. Vendor names. AI pattern tokens. Plaid transaction data. Per-row diffs.

Roadmap

Batch Scope Status
1 Tauri scaffold + data classification table Landed
2 HybridRouter backend + sync-settings UI Landed
3 E2EE reviewer handoff (Option B per architecture review) Planned
4 Aggregates reducer + /stats/{period} endpoint contract Planned
5 OAuth broker for ERP integrations (Xero / QuickBooks / NetSuite) Future

Batches 2+ stay disabled at runtime until a real user demand surfaces. The local product is the product; the cloud is the convenience.


Building from source

Desktop (Tauri)

Prerequisites:

Platform What you need
All Rust toolchain (stable, 1.77+)
All Node.js 18+ (just for the Tauri CLI)
Windows "Desktop development with C++" Visual Studio workload + WebView2 (preinstalled on Win11)
macOS Xcode command-line tools (xcode-select --install)
Linux libwebkit2gtk-4.1-dev, libssl-dev, libgtk-3-dev, libayatana-appindicator3-dev, librsvg2-dev (or distro equivalents)

One-time setup:

npm install                        # installs @tauri-apps/cli
npm run tauri:icons                # only if you have a 1024x1024 source PNG ready

Dev — runs the desktop app with hot reload of the webview when you edit src/*:

npm run tauri:dev

Release build — produces a code-signed installer (.msi on Windows, .dmg on macOS, .deb / .AppImage on Linux) under src-tauri/target/release/bundle/:

npm run tauri:build

Browser-only (dev / debugging)

No build step. Serve the static files:

python -m http.server 8000
# http://localhost:8000/

ES modules are blocked over file:// in Chromium, so a server is required. Any HTTP server works (npx serve, caddy file-server, etc.).

Optional — vendor SheetJS for the offline build

For the desktop build to work without a network connection, drop SheetJS into vendor/:

curl -L https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js \
     -o vendor/xlsx.full.min.js

index.html probes for the vendored copy first and falls back to cdnjs if it isn't there. .gitignore excludes the binary so it isn't checked in.


Storage backends

The frontend always calls one set of functions — kvGet / kvSet / kvRemove / kvList. What they do under the hood depends on what's available at boot:

Detector Backend Where data lives
window.__TAURI__ present TauriBackend SQLite at app_data_dir/greenbar.db
<meta name="app-api-base"> set (browser only) RemoteHttpBackend REST API at the configured base URL
Neither LocalStorageBackend Browser localStorage

The desktop build always picks TauriBackend. The other two are for dev / evaluation. The Batch 2 HybridRouter will sit above TauriBackend and selectively forward sync-eligible writes to a remote, leaving raw data classes pinned to SQLite.

Sync Settings (admin-only)

Sign in as an administrator → the header gains a 🔁 Sync button. The panel exposes:

  • Endpoint URL — runtime override for <meta name="app-api-base">. Save → reconfigure happens immediately (no app restart).
  • Test connection — one-shot GET /kv?prefix=__probe__ to verify reachability + auth.
  • Per-class toggles — one switch per sync-eligible store. Defaults all OFF.
  • Push / Pull buttons per enabled class for first-enable bootstrap and on-demand refresh.
  • Last push / last pull timestamps so it's clear which side is the latest anchor.
  • A collapsible list of every LOCAL_ONLY store (hard-coded, never syncs — for transparency).

Conflict policy in Batch 2: last pull wins. The server overwrites local on pull; local overwrites server on push. Per-key versioning (vector clocks / E2EE blobs) lands with Batch 3.

Default behaviour: with everything OFF (factory default), the HybridRouter is transparent — every write goes only to the local backend (Tauri SQLite or browser localStorage). The cloud is never touched until an admin opts in.

Optional REST backend contract (browser mode)

When <meta name="app-api-base" content="https://api.example.com"> is set, the browser build expects four endpoints:

Method Path Body Response
GET /kv?prefix=<p> 200 [{key, value}, ...]
GET /kv/{encodedKey} 200 value | 404
PUT /kv/{encodedKey} JSON 204
DELETE /kv/{encodedKey} 204 | 404

Requests carry Authorization: Bearer <session-token> when a user is signed in. Server is responsible for per-tenant keyspace isolation.


Security posture

  • Local-first by default. Desktop builds keep raw data in SQLite at the OS user-data location; no network unless you opt into a cloud feature.
  • HTML escaping (esc()) on every user-supplied string interpolated into innerHTML, including the anomaly engine output.
  • CSV / formula injection guard (safeCell / safeRow) on every XLSX export.
  • Content-Security-Policy — stricter in the desktop build (no cdnjs, no plaid.com); deploy-time configurable allow-list in the browser build.
  • SheetJS pinned with SHA-384 SRI integrity + crossorigin="anonymous" when loaded from cdnjs; can be fully vendored for offline use.
  • File-upload guards — 25 MB cap, .xlsm / .xlsb / .xltm rejection, extension allow-list, FileReader.onerror handlers, raw:false XLSX parsing.
  • Workflow integritywfReopen clears preparer attestation and checklist so re-submission requires re-attestation.
  • Cross-tab storage event listener — approvals in one tab refresh the others.
  • Auth — local user registry; PBKDF2 (200k iterations, SHA-256, per-user salt) via SubtleCrypto. Sessions auto-expire after 8 hours of inactivity, extended by user activity (mouse / keys, throttled).
  • Role gates — preparer / reviewer / administrator. Same person can't be preparer and reviewer of the same period.
  • Plaid — opt-in; the browser never sees the Plaid access token (server-side only).

Known limitations

  • Browser-mode localStorage is unencrypted; don't use it on shared devices.
  • Auth in the local product is attribution, not real authentication. Anyone with file-system access to greenbar.db (or, in browser mode, the localStorage profile) can read or edit the user registry. Cloud sync (Batch 2+) introduces real server-side identity.
  • Modal a11y (focus trap, Esc to close, aria-modal) is not yet implemented.
  • fParseCSV regex emits spurious empty cells for embedded-comma quoted fields. Fine for typical exports; not RFC-4180 compliant.
  • SheetJS 0.18.5 is pinned with SRI but predates CVE-2023-30533 (prototype pollution). Bump to 0.20.x when convenient.

Plaid integration

Optional. Network required. Opt-in in the desktop build — the Plaid panel is hidden until you explicitly enable it under sync settings (Batch 2).

Plaid credentials never live in the browser. The page loads only Plaid Link (an iframe from cdn.plaid.com); your backend holds client_id / secret and brokers every Plaid REST call. Four endpoints are required:

Method Path In Out Plaid call
POST /api/plaid/link-token {entityId, accountId, scope} {link_token} /link/token/create
POST /api/plaid/exchange {entityId, accountId, scope, publicToken, metadata} {item_id} /item/public_token/exchangeserver stores access_token
POST /api/plaid/sync {entityId, accountId, scope, itemId, cursor} {added, modified, removed, next_cursor} /transactions/sync
POST /api/plaid/unlink {entityId, accountId, scope, itemId} 204 /item/remove

Plaid sends amount with positive = outflow; this app uses the opposite convention. ingestPlaidDiff() flips the sign — don't double-flip server-side.


Project structure

index.html                      Entry page — HTML body + inline on*= handlers (CSP-allowed)
styles/main.css                 All styles
package.json                    npm scripts for Tauri (dev/build/icons)
.gitignore                      Ignores Tauri target/, node_modules/, vendored SheetJS

src-tauri/                      Tauri host (Rust)
  Cargo.toml                      tauri 2, rusqlite (bundled), parking_lot
  build.rs                        tauri-build glue
  tauri.conf.json                 window, CSP, bundle config
  src/main.rs                     kv_get / kv_set / kv_remove / kv_list + data_location
                                    Backed by SQLite at app_data_dir/greenbar.db (WAL + idx)

src/
  main.js                       Boot — wires every handler onto window, runs init
  state.js                      Shared mutable state singleton
  utils/
    dom.js                        esc() XSS guard, safeCall window helper, getBalances
    format.js                     fmt, parseAmt, toast, fileGuard, MATCH_EPS, …
    parsers.js                    Date/number parsing + column auto-mapping (formatter)
    strings.js                    Tokenisation, Levenshtein, tokenSetRatio (matching)
    dates.js                      Timezone-safe date helpers (matching)
  services/
    storageBackend.js             kv API + 3 backends (Tauri / Remote / Local) + reconfigureSync
    dataClassification.js         Per-key privacy class (Batch 1)
    syncPrefs.js                  Per-device sync settings (Batch 2 — LOCAL_ONLY)
    hybridRouter.js               Per-class routing wrapper around local + remote (Batch 2)
    storage.js                    Outstanding-items store; global purge
    matching.js                   4-rule matching engine + suggestion engine
    reconciliation.js             Core — rRun, rReconcile, rApplyRes, learning hook
    workflow.js                   Maker/checker approval workflow + role gates
    auth.js                       Local PBKDF2 user registry + sessions
    entities.js                   Entity + account CRUD + scope routing + migration
    templates.js                  Saved column-mapping presets
    aiResolutionEngine.js         Local pattern learning + bulk auto-resolve
    anomalies.js                  Z-score / IQR / duplicate / sign / weekend detection
    reporting.js                  Standard XLSX exports + template downloader
    auditReport.js                11-sheet audit-ready XLSX
    plaid.js                      Plaid Link + sync (opt-in, network required)
  components/
    header.js                     App switcher + contextual header actions
    toast.js                      Re-exports toast() (lives in utils/format.js)
    balanceBar.js                 Balance proof box
    formatter.js                  Formatter wizard (drag/drop, mapping, format, export)
    importPanel.js                Reconciler drop zones + preview renderer
    resultsTable.js               Results table + tab dispatcher (rShowTab)
    exceptionsPanel.js            Exceptions tab + resolution log
    outstandingPanel.js           Outstanding-items tab + per-period actions
    resolutionDrawer.js           Inline "Resolve" drawer (incl. AI + match suggestions)
    modals.js                     Force-match / suggestion / combine dialogs
    anomaliesPanel.js             Anomalies tab renderer
    reportPanel.js                Final report renderer
    aiSuggestionsPanel.js         AI suggestion panel + bulk auto-resolve modal
    entityPicker.js               Header entity/account dropdowns
    entityManager.js              Entity + account CRUD modal
    authPanel.js                  Header chip + sign-in / create-user / user manager
    syncSettingsPanel.js          Sync settings modal (Batch 2 — admin only)
    plaidLinkButton.js            Plaid status panel (Import tab)

legacy/                         Pre-refactor single-file build (reference only)
vendor/                         Optional: vendored SheetJS for offline desktop builds

License

Internal — see repo settings.

About

Local-first bank reconciliation with SHA-256-hash-chained audit ledger, role-gated maker/checker workflow, and optional sync to a self-hosted server.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors