Every finance team runs its month-end close over email and a shared spreadsheet, and every month the CFO asks the same two questions nobody can answer from either: where is the close stuck right now? and why did it take six working days instead of five? TieOut is the control tower for that close: the checklist with its dependencies, sign-offs with separation of duties enforced by the system, a working-day clock (BD+n) that skips weekends and holidays, bottleneck analytics that name the slowest category, and an append-only audit trail that turns "what happened in March?" into a query instead of an archaeology dig.
It is a Blazor web app over PostgreSQL, with EF Core on the command side and Dapper serving the analytics SQL, and every rule it enforces is tested against a real database and a real browser.
Reading this page. Finance leader? The screenshots and the bold lines below are the whole story. Engineer? TECHNICAL.md holds the architecture, the EF Core + Dapper decision record, and the test ladder, including the parity tests that pin the SQL analytics to the same answers the C# domain computes.
The close is a relay race: AP, AR, payroll, prepaids, bank recs, intercompany, tax, then the GL review and the controller's certification. It fails as a spreadsheet because the spreadsheet cannot know three things TieOut knows structurally:
| The spreadsheet cannot | TieOut does |
|---|---|
| Tell you a task is blocked, or by what | Every dependency is declared; a blocked card names exactly the tasks it waits on, and the server refuses completion until each is signed off |
| Enforce that the person who did the work is not the person who approved it | Four-eyes is a rule: whoever completes a sign-off-required task cannot sign it off, in the UI or the API, no exceptions |
| Tell you the true cost of a reopen | Every reopen demands a reason, kept verbatim, timestamped on the working-day clock, and feeds the bottleneck analytics |
And the metric the CFO actually owns, days-to-close, is computed on working days from month end (BD+n, holidays excluded) rather than wall-clock days, so a close that "took nine days" across Labor Day is honest about what nine days means.
![]() |
![]() |
The demo company is Cedar Ridge Foods, Inc.: seven finance staff, five locked months of real-looking history (including a painful June), and one live August close mid-pain, with an intercompany task reopened, four sign-offs waiting, and thirteen tasks late. Reset it any time; the seed is deterministic, so every screenshot and test tells the same story.
- Dependencies gate completion. The controller certification cannot complete while the subledger tie-outs are unsigned, and a task waiting for sign-off does not satisfy its dependents. Refusals are typed, carry the reason in plain English, and write nothing.
- Four-eyes sign-off. The completer is recorded and can never be the signer. The board greys its own case out for you; the API answers 409 with
FourEyesViolation. - A period locks only when everything is completed, and only by the Controller or CFO. Locking writes the actual close day in working days next to the target, and a locked period is frozen: reopens and completions are refused with the period named.
One command side, one read side, one rule core:
- EF Core writes. Every state change (complete, sign off, reopen, comment, lock) loads what it needs, asks the pure
CloseRules, applies the change, appends its audit event, and saves both in one transaction. A refused rule writes nothing. - Dapper reads. The dashboard's trend, bottleneck, and late-list panels are served by reviewed SQL, with working days computed in the database (generate_series minus weekends minus the holiday table). Integration tests pin that SQL to the same answers the C#
WorkingDayCalendarproduces, so the two data paths cannot drift. - Blazor renders. Static SSR for the dashboard and audit trail, one Interactive Server island for the board where the actions live, and a hand-built dark design system (no CSS framework, no chart library: the trend chart is SVG the app draws itself).
The proof ladder: 30 domain-rule tests (working-day math, four-eyes, gates), 14 integration tests on real PostgreSQL (including the Dapper/EF parity contracts and a full close walked to lock in dependency order), 9 Playwright tests in a real browser, and an 11-request newman regression against the booted app. scripts/run-e2e.sh boots the whole thing against a scratch database and runs the last two in one command.
Requires .NET 10 SDK and PostgreSQL 17 (any local instance; the app creates and migrates its database on first boot):
git clone https://github.com/Alex5350/tieout.git
cd tieout
export TIEOUT_DB="Host=localhost;Port=5432;Database=tieout_dev;Username=<your-user>"
dotnet run --project src/TieOut.AppOpen http://localhost:5008 (or the port it prints), act as Maria Vance (Controller) from the header switcher, and drive the August close. To prove the whole stack end to end, including the browser tests:
./scripts/run-e2e.sh # boots a scratch app + database, replays newman, runs Playwright| Component | Version |
|---|---|
| Runtime and language | .NET 10, C# 14 |
| UI | Blazor Web App (Static SSR + Interactive Server) |
| ORM, command side | EF Core 10 (Npgsql provider 10.0.3) |
| Micro-ORM, read side | Dapper 2.1.79 |
| Database | PostgreSQL 17 |
| Tests | xUnit, Microsoft.Playwright 1.62, newman 6 |
src/TieOut.Domain- the close's entities, working-day calendar, and rules; zero dependenciessrc/TieOut.Data- EF Core context and migrations,CloseStore(commands),AnalyticsQueries(Dapper reads), deterministic seedersrc/TieOut.App- Blazor UI, typed API, cookie-based demo identitytests/- domain and integration suites (the latter includes the parity contracts)e2e/- Playwright suite against the booted apppostman/- the newman regression collection- docs/errors.md - every typed refusal the API can return, and when
Demo identity is a cookie switcher (a real deployment wires SSO and keeps everything else identical). Reopens do not cascade to dependents that already completed; the audit trail surfaces the risk instead. Multi-entity consolidation, journal entry creation, and ERP integrations are deliberately out of scope: this is the close's control plane, not the ledger (for the ledger, see LedgerLite in the same portfolio).
MIT. Cedar Ridge Foods, Inc. is fictional; every number in the demo is generated by the committed seeder.


