-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Canonical reference: docs/architecture.md, docs/libraries.md, docs/coding-standards.md.
| 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.
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.
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-dbschema tree, then regenerate the SQL the API embeds. - The API's
SchemaBootstrapperprovisions 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-deployschema 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.
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.
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.
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.
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.
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 underforge.tests/Architecture/cover capability gating on controllers and source standards (clock usage, try/catch in controllers, file size). -
forge-ui—npm run lint:standardscovers 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.
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.
Forge · Apache 2.0 · built by Armory Works — this wiki maps the docs; the authoritative detail lives in docs/.
Evaluating
Running it
- Installation
- First Week
- Configuration and Integrations
- Hardening a Production Install
- Backup and Restore
- Upgrades and Rollback
- Accounting Modes
Using it
- App Surfaces
- Shop Floor Kiosk
- Mobile and Offline
- Access and Roles
- Customizing an Install
- Feature Reference
- UI Flows
Building on it
- API Access
- Architecture
- Capability Gating
- Workflow, Gates and Approvals
- Data Ownership and Export
- Glossary
Contributing
Repo wikis