A hierarchical job-tracking system with dynamic, historically-accurate costing.
Live Google Cloud Run demo with login demo and password demo-jobtrack-1234. Note the instance may take a couple of seconds to warm up when stopped, and recycles when not in use (the database is reset to a default state).
This is a recreation of my original project running on SQL Server and .NET 4.8, now rebuilt from scratch on PostgreSQL (or SQLite) and .NET 10, and packaged as a docker image for easy deployment.
The app has 2 unusual features:
- work is organised into branches and leaves, with a single root node; the tree can be arbitrarily deep, and any node can be moved to a new parent without losing its history or breaking its cost calculations. Actual work is done in leaves, which can be paused and resumed, and worked on by several people concurrently. See this tree image
- cost is computed live, according to work schedules (with overrides), and concurrent work is split fairly across all participants. See costing-engine.md and rate-resolution.md. Job cost is never stored, and can be recomputed at any time for any node in the tree, even if the tree has been restructured since the work was done.
Additionally, all jobs can have prerequisites, and the system will automatically prevent work from being started on a node until all its prerequisites are complete. As with other features, this applies to both branches and leaves. Since branches don't have work themselves, a branch is considered complete when all its leaves are complete.
Two database backends are supported:
- PostgreSQL is the production backend, and is used in the live Google Cloud Run deployment. Supports multi-instance concurrent writes from multiple web hosts.
- SQLite is a fully conforming second provider, intended for embedded and demo use. Writes are always serial in SQLite by design, and we use WAL to ensure writes don’t block concurrent reads.
- Stack: .NET 10, C# 14, EF Core 10, Noda Time, ASP.NET Core Identity, Postgres/SQLite.
- Shape: a database and a reusable library, with three clients over them — see Architecture.
- Two databases, one behaviour: PostgreSQL is the production backend; SQLite is a fully conforming second provider for embedded and demo use, held equivalent by a shared contract-test suite.
- Performance: hundreds of thousands of jobs, and years of history have been planned for.
Budgets and evidence:
docs/traceability/performance-budgets.md. - Security: from first principles, defence-in-depth hardening, split least-privilege credentials, audited
administrative actions, two-factor authentication, and a maintained threat model with
every mitigation tied to a named test:
docs/threat-model/web-authentication-threat-model.md.
Every leaf shows one status. The short form appears where space is tight (a phone, a dense tree row);
the full word appears elsewhere. Full reference: docs/behaviour-overview.md.
| Status | Short | Meaning |
|---|---|---|
| Active | (time) / N active |
Someone is working it right now |
| Waiting | Wait |
Open, not worked yet — Start begins the first session |
| Paused | Paused |
Started, nobody clocked on right now — Start resumes it |
| Unack | Unack |
A requester's submission staff have not accepted yet |
| Success | Succ |
Finished successfully (the only outcome that satisfies a prerequisite) |
| Cancelled | Cancel |
Withdrawn without completing |
| Unsuccessful | Unsucc |
Attempted but did not succeed |
| Closed | Closed |
Archived |
A leaf whose prerequisites are unmet also carries a blocked marker over its status; work cannot start until they reach Success.
Branches carry no leaf status of their own — their status is computed from the leaves beneath them (recursively) and collapses to two states:
| Status | Meaning |
|---|---|
| Success | Every leaf in the branch has succeeded |
| Unfinished | At least one leaf has not succeeded yet (the ordinary in-flight state) |
More details: docs/architecture-overview.md.
Ports and adapters (hexagonal), close to Clean Architecture with two departures: the database is a layer that enforces its own invariants, not a detail hidden behind a repository; and the library exposes one coarse facade, not an interface per use case.
Five layers, built and depended on strictly bottom-up. The database and library stack; the three
clients above them are siblings, each calling IJobTrackClient in-process — the web client does
not go through the HTTP API:
- Database — PostgreSQL (production) or SQLite (eg embedded and demo use) as numbered
forward-only SQL scripts applied by
JobTrack.Database. - Reusable library — the domain, use cases and both EF Core persistence providers, behind the
single
IJobTrackClientfacade; persistence is inverted, so each provider implements ports the application layer declares. - HTTP API — a token-authenticated versioned JSON API. This is for external clients; the web client does not need it. A future mobile app or SPA would connect here.
- Web client — mobile-friendly as a first principle. Server-rendered Razor Pages rather than an SPA, to maximise compatibility: it works without client-side state on legacy browsers, and is intentionally conservative.
- Admin CLI — uses the same library, to bootstrap the first administrator, create employees, emergency password and 2FA resets, and job-tree import.
The HTTP API and the web client share the one JobTrack.Web process; the admin CLI is its own
executable. Each process picks a database provider at startup and then reaches the database only
through IJobTrackClient — the dependency rules are asserted by the tests in
tests/JobTrack.ArchitectureTests/.
| If you want to… | Read |
|---|---|
| Build, test, run, or administer it locally | docs/developer-guide.md |
| Understand how it behaves for its users | docs/behaviour-overview.md |
| See the architecture and layers file by file | docs/architecture-overview.md |
| Deploy or operate it | docs/operations/postgresql-cloud-run-deployment.md |
Lines of code as counted by tokei (blank lines and comments
excluded), as of 19 August 2026:
| Area | Files | Lines of code |
|---|---|---|
Product — src/ |
733 | 38,016 |
Tests — tests/ |
445 | 72,854 |
Database schema — database/ |
43 | 2,399 |
Sample API client — samples/ |
19 | 1,171 |
The 72k lines of test code are a consequence of Test Driven Development, with over 4,000 tests in the full suite. It takes up to 10 minutes to run, even on a fast Mac.
Files are held to hard size ceilings by an architecture guard: 1000 code lines for product and sample C#, 2000 for test C#, and 500 physical lines for Razor. A C# code line is one carrying at least one token, so comments and blank lines are free. Methods are capped at 75 executable lines.
A short test script, aiming to complete in about 20 seconds, is used for pre-commit checks scripts/fast-test.sh
Current release: v1.2.0 (2026-08-24) — merges the Active column's Unstarted leaf status into Waiting (ADR 0070), since starting an unstarted leaf auto-attaches its work record and the two read identically to a user, and adds an end-user leaf/branch status reference. No schema or contract changes. Full history: CHANGELOG.md.
Release-ready. All four delivery gates — database, reusable library, web application, and release — have formal, source-controlled acceptance records (ADR 0025, 0026, 0027, 0063). The codebase was built test-first throughout (about 1.9 lines of test for every line of product code), passes its full solution and performance suites, and has been through three internal security audits, each fully remediated. Performance is enforced: measured budgets on a 200,000-node production-shape database run as regression ceilings on every performance-suite run.
The production deployment is PostgreSQL on Google Cloud (Cloud Run + Cloud SQL, with automated backups and point-in-time recovery), defined by ADR 0062.
Live Google Cloud Run demo (SQLite backend — a demonstration configuration, not production).
The SQLite backend can be run in a throwaway docker container or as a persistent local database. See docs/operations/sqlite-limitations-and-configuration.md for its limitations.
Design and specification
docs/jobtrack_spec_codex.md— normative specification (docs/jobtrack_spec_claude.mdsupplements it).docs/database-entities.md— core entities and the costing algorithm.docs/costing-engine.md— the cost engine in depth: the boundary-partition algorithm and1/Nconcurrency allocation worked through a three-deep overlap, the PostgreSQL range column and GiST indexing behind it, and the EF Core materialization strategy.docs/rate-resolution.md— how one worker's hourly rate at one instant is resolved: the rota and schedule exceptions that decide eligibility, then the four-level precedence (priced overtime, nearest-ancestor node override, effective-dated user rate, default).docs/api/jobtrack-client-design.md— theIJobTrackClientfacade;docs/api/external-http-api-reference.md— HTTP routes and auth model.docs/design-language.md— the "Console" visual design system.docs/ownership-model.md— node ownership, the unassigned pickup pool, and work authorization.docs/requester-user-guide.md— submitting and tracking work as aRequester.
Operations, security, and traceability
docs/operations/postgresql-cloud-run-deployment.md— the production deployment (ADR 0062): provisioning, schema upgrades, rotation, emergency reset.docs/operations/production-deployment.md— the alternative self-hosted single-server runbook (ADR 0014).docs/operations/postgresql-backup-restore.md— backup/restore procedure and the smoke test that proves it.docs/operations/docker-image.md— the throwaway SQLite demo container.docs/operations/local-live-instance.md— a single persistent local database for your own use.docs/operations/sqlite-limitations-and-configuration.md— SQLite's operational envelope and required per-connection configuration.docs/operations/web-host-security.md,docs/threat-model/web-authentication-threat-model.md— host hardening and the authentication threat model.docs/operations/browser-testing.md,docs/operations/hurl-smoke-tests.md— the Playwright end-to-end suite and thetests/hurl/HTTP smoke suites.docs/operations/job-tree-import.md—AdminCliimport-tree: JSON format, validation, examples.docs/operations/global-tools.md,docs/operations/mutation-testing-gate.md,docs/operations/package-metadata-gate.md— required tooling and quality gates.docs/traceability/— test budgets, performance/scale budgets, and spike findings.
AGPLv3. See LICENSE and THIRD-PARTY-NOTICES.md.
