Skip to content

Architecture

Daniel Hokanson edited this page Aug 30, 2026 · 4 revisions

Canonical reference: docs/architecture.md, docs/libraries.md, docs/coding-standards.md.

The stack

Layer Technology Container
Frontend Angular + Angular Material — zoneless, signals, standalone components; served by nginx forge-ui
Backend .NET Web API — MediatR/CQRS, FluentValidation, Mapperly, Serilog, Hangfire, SignalR forge-api
Database PostgreSQL + pgvector; schema owned by forge-db forge
Object storage MinIO (S3-compatible) forge-storage
Backups Scheduled pg_dump sidecar forge-backup
Optional Ollama (AI), Coqui (TTS), DocuSeal (signing), Seq (logs), GlitchTip (crash reports) profile-gated

Also in the mix: Three.js for inline STL rendering, and Playwright and Vitest for tests.

An interactive API reference (OpenAPI plus a Scalar UI) is served by the API itself, and only when ASPNETCORE_ENVIRONMENT is Development — which is what a stock install ships. Setting Production, recommended for a real deployment, removes it. See API Access.

Backend layering

forge-api is layered with a genuinely pure core — forge.core has zero project references and holds only entities, enums, interfaces, models and settings.

forge.core          entities / enums / interfaces / models / settings   (no project refs)
forge.data          EF Core DbContext + persistence          → core
forge.integrations  external providers (accounting, shipping, AI, …)  → core
forge.api           features, controllers, capability gates  → the bulk of the code
forge.tests         unit + integration + architecture tests

Features are MediatR request/handler pairs under Features/; controllers are thin. Mapping is source-generated with Mapperly — no runtime reflection mappers. Background work runs on Hangfire with the PostgreSQL storage provider.

Schema ownership: forge-db, not EF migrations

This is the single most important architectural fact to know before touching the database.

There are no EF Core migrations. The desired-state schema is a tree of one-object-per-file SQL scripts in forge-db, reconciled onto a live database with stripe/pg-schema-diff. EF Core is present purely as the query-mapping layer.

  • Schema changes = edit the forge-db schema tree, then regenerate the SQL the API embeds.
  • The API's SchemaBootstrapper provisions a fresh database from that assembled schema and is a no-op on an existing one — it does not reconcile drift.
  • Upgrades of a populated install go through the forge-deploy schema reconcile step — which is gated on a flag that ships off, and is auto-enabled only on release deploys. See Upgrades and Rollback.
  • Consequence for developers: a stale local database volume will not self-heal. Reconcile it with the forge-db harness rather than expecting startup to fix it.

The reason for the split is that some of the schema cannot be expressed by an ORM migration generator at all — the vector-search extension behind document search, and the database triggers that make posted accounting entries immutable.

The IClock rule

Time is injected, never read from the ambient clock. IClock (forge.core/Interfaces/IClock.cs) has a SystemClock for production and a SimulationClock for end-to-end tests, which is what makes deterministic multi-week simulation runs possible. It is injected into AppDbContext's timestamp stamping and into time-dependent handlers. New code calling DateTime.UtcNow directly is a standards violation.

Authentication

ASP.NET Identity with JWT bearer tokens for the SPA. Roles are additive — a user can hold several, and the seeded set is fixed rather than something an admin extends; see Access and Roles.

There is no refresh token. Forge issues one signed JWT and rotates it in place: refreshing swaps the session identifier (jti) registered against the token, and every request validates that jti against the session table, so revocation survives an API restart. The mechanics an integrator needs are on API Access.

Authentication is tiered, because a shop floor and an office need different things:

Tier Method Where
1 RFID/NFC scan + PIN Kiosk (primary)
2 Barcode scan + PIN Kiosk (fallback)
3 Username + password Desktop / mobile
4 Enterprise SSO — Google, Microsoft, generic OIDC Optional

Admins create accounts and issue a setup token; the employee completes their own password, PIN and profile. An admin never views or sets a password or PIN — a reset issues a new setup token instead.

OAuth tokens for the accounting provider are held on a single company-level connection and encrypted via the ASP.NET Data Protection API, with keys in Postgres.

Real-time and background work

SignalR carries the live surface over five hubs — /hubs/board, /hubs/notifications, /hubs/timer, /hubs/chat and /hubs/accounting — with reconnection handling and a connection banner in the UI. They sit outside the api/v1 prefix and authenticate by query-string token, since a WebSocket cannot set an Authorization header; API Access has the mechanics. Hangfire runs the scheduled and deferred work, in-process in the API container — see Limits and Non-Goals for what that means for scale.

Pluggable accounting

IAccountingService is a common interface over customers, invoices, estimates, POs, payments, time activities, employees, vendors and items. AccountingServiceFactory resolves the active provider from settings. Each provider owns its auth flow, API client and DTO mapping; the sync queue, caching and orphan detection are provider-agnostic. See Accounting Modes — the behavioural consequences are large enough to deserve their own page.

Standards are enforced, not just written down

Forge's coding standards are backed by tests and lint rules rather than prose alone, using a consistent pattern: hard rules fail the build; legacy debt is held by a per-file ratchet. New files must be clean, already-baselined files may not get worse, and improving a file fails the check until you regenerate and commit the baseline in the same commit. Baselines are never hand-edited upward.

  • forge-api — architecture tests under forge.tests/Architecture/ cover capability gating on controllers and source standards (clock usage, try/catch in controllers, file size).
  • forge-uinpm run lint:standards covers console logging, hardcoded colours, unjustified !important, inline templates and raw form controls.

Before pushing UI work, run the local gates — Contributing carries the exact CI sequence, which is longer than it looks. Shipped translations live only at public/assets/i18n/{en,es}.json with enforced 1:1 parity between locales; that is a contributor rule about the build-time catalogs. At runtime an install registers its own languages and can override any label without a rebuild — see Customizing an Install.

Four things called "workflow"

Forge has four distinct mechanisms that share the word, answering four different questions: the gated sequence engine (may this step proceed?), workflow definitions and runs (where is this user in a multi-step form?), approval workflows (who has to say yes?) and status entries (what state was this in, and when?). Workflow, Gates and Approvals disambiguates them.

Clone this wiki locally