Architecture Overview
This page describes the architecture of TruePPM as it exists today. The scheduling engine, API, real-time layer, web frontend, and the 0.2 settings/administration and program platform are all functional as of 0.3 — the latest shipped pre-release is 0.3.0-alpha.1 (June 28, 2026), which layered the agile-team feature set and the v2 interface refresh on top of the 0.2 settings/administration and program platform.
System diagram
Section titled “System diagram”How to read it. Solid arrows are direct PostgreSQL reads and writes;
dotted arrows are asynchronous messages passed through Valkey, which is both
the Celery broker and the Django Channels layer. PostgreSQL stores every
project, task, and the WBS hierarchy as an ltree column with a GiST index for
subtree and ancestor queries.
Follow a schedule change end to end: a write through a DRF ViewSet enqueues a reschedule on the Valkey broker; the Celery worker runs the CPM engine, writes the new dates to PostgreSQL, and publishes the result back through Valkey; Django Channels picks that up off the channel layer and fans it out to every connected client over its WebSocket.
Key design decisions
Section titled “Key design decisions”API-first
Section titled “API-first”Every feature is a REST or WebSocket endpoint before it is a UI element. Web and mobile clients have no privileged access — they are API consumers identical to any third-party integration. The OpenAPI schema at /api/schema/ is the authoritative contract.
The authoritative value of any persisted fact — including every scheduled date, float, and Monte Carlo forecast — is always computed server-side and reached over the API. Three narrow compute paths deliberately run outside the API, each bounded by one invariant: the server always has the last word.
- The interactive schedule preview. Dragging a Gantt bar (or rescheduling with the keyboard) recomputes the affected downstream dates in the browser with no API round-trip, because a request per pointer-move could never stay interactive. That preview is a best-effort, lower-fidelity estimate — it is never persisted, and the authoritative server CPM reconciles the real dates on commit.
- On-device / offline recompute. When there is no network, the API cannot be first, so the client will recompute locally. This is what the Rust/WASM CPM engine is for: it is held in conformance with the Python engine in CI so an offline result will match what the server would compute, and the server still reconciles on reconnect. Wiring that engine into the browser and mobile app is future work (#1777) — the shipped preview above runs a TypeScript CPM port until then.
- The engine as a library.
trueppm-schedulerand its Rust sibling are usable with no API at all (see Scheduling as a separate package).
The rule of thumb: if a value is authoritative — if a persisted fact depends on it — it lives server-side behind the API. If it is a discardable preview or an offline stand-in the server will reconcile, it may run at the edge. See ADR-0599 for the full boundary.
Computed, not guessed
Section titled “Computed, not guessed”The AI-native foundation — see AI-native by design for the consolidated view aimed at agent builders. Every incumbent is bolting an LLM onto a project database and letting the model guess dates. TruePPM takes the opposite stance, and it has a name: computed, not guessed. An AI-surfaced answer is never the language model’s opinion — it is a CPM or Monte Carlo computation the engine performed, carrying a server-side derivation you can cite. The model’s only job is to translate a question into an engine call and to phrase the engine’s answer back in natural language. It never supplies the number.
This is an architectural commitment, not a feature toggle. It is why the scheduling engine is a separate, deterministic package and why every feature is an API fact first: if a value is computed server-side and reachable over the API, an agent can retrieve it and cite it; if it lived only in a chat prompt, the agent could only guess at it.
Incumbent — the LLM is the answer:
question ─▶ LLM ─▶ asserted answer (a plausible guess; no derivation to check)
TruePPM — the engine is the answer: "computed, not guessed"
question ─▶ NL layer ─▶ engine call ─▶ provenance-carrying answer (translates (CPM / Monte ("P80 is Oct 22, derived from to a call) Carlo computes) this critical chain" — citable)The principle is sequenced across the roadmap as one capability with four parts — compute, cite, refuse, reproduce — not four scattered AI bullets (see the roadmap):
- Compute / cite — provenance graph (#1058) — every computed date, float, and
P80 carries the derivation an agent can cite, so an answer is explainable, not
asserted. It lands with the 0.4 read-only MCP server and
is already merged to
main. - Reproduce — agent-action audit foundation (#1805, ADR-0112
Accepted) — every agent read and every verdict is recorded in a hash-chained,
audit_verify-checkable log; it also lands with the 0.4 beta and is already inmain. A signed engine-version + input-hash answer stamp (#1065) follows at 0.9. - Natural-language query layer (#1060 #1061, planned for 0.5) — compiles a question into engine calls, never into an answer; the model translates, the engine answers.
- Refuse — safe agent writes (#1062–#1064, planned for 0.6) — an engine-as-referee refuses any agent write that would create an impossible schedule, identically to a human write; this is the refuse verb reaching the write side.
The deterministic engine behind all four verbs is shipped today; the compute, cite,
and reproduce foundations land with the 0.4 beta and are already in main. The dates
above are targets, not commitments — the roadmap is the source
of record for what has shipped versus what is planned.
Offline-first sync protocol
Section titled “Offline-first sync protocol”The API exposes a WatermelonDB-compatible sync endpoint (GET /api/v1/projects/{pk}/sync/) returning changes and deleted arrays keyed by server_version. This is designed for future mobile and PWA clients — see Offline Sync for details.
Scheduling as a separate package
Section titled “Scheduling as a separate package”The CPM and Monte Carlo engine lives in packages/scheduler (trueppm-scheduler on PyPI), completely independent of Django. This means:
- The engine can be used without the API (embedded in other tools and scripts); a Rust sibling (
packages/wasm-scheduler) compiles to WASM and is held in conformance with it in CI, for future on-device scheduling (#1777) - The engine has its own test suite and release cycle
- Algorithmic correctness can be validated without a running database
The Celery worker imports trueppm-scheduler as a library, fetches project data from PostgreSQL, calls schedule(), and writes CPM output fields back.
Versioned models and soft delete
Section titled “Versioned models and soft delete”Every synced model extends VersionedModel:
class VersionedModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4) server_version = models.BigIntegerField(default=0) is_deleted = models.BooleanField(default=False, db_index=True) deleted_version= models.BigIntegerField(null=True, blank=True)server_version starts at 1 on INSERT and increments atomically on every UPDATE via an F() expression to avoid lost-update races. Deletes are soft: the row is retained with is_deleted=True so mobile clients receive a tombstone on the next sync pull.
Real-time broadcasts
Section titled “Real-time broadcasts”Every mutation is followed by a broadcast_board_event() call deferred inside transaction.on_commit(). This ensures:
- The broadcast only fires if the database transaction committed successfully
- WebSocket clients receive the event as a push, not a poll
Packages
Section titled “Packages”packages/scheduler
Section titled “packages/scheduler”Pure-Python. Dependencies: networkx (graph), numpy (Monte Carlo). Ships on PyPI as trueppm-scheduler. See Scheduling as a separate package above — this page does not document the engine’s internals.
packages/wasm-scheduler
Section titled “packages/wasm-scheduler”The CPM engine’s Rust sibling: a petgraph-based implementation compiled to WebAssembly with wasm-pack (crate trueppm-wasm-scheduler). It exists for future on-device / offline scheduling (#1777) — a browser or mobile client with no network still needs to recompute dates locally. CI holds it in behavioral conformance with packages/scheduler (wasm:conformance), and gates it with wasm:lint (clippy -D warnings), wasm:test, and wasm:license-check (deny.toml). It is not yet wired into the web or mobile client — see API-first for why an unwired offline engine doesn’t compromise the “server always has the last word” invariant.
packages/mobile
Section titled “packages/mobile”React Native (0.81) + Expo tooling, TypeScript, NativeWind (Tailwind for React Native). Offline-first by design: src/db/ holds the on-device WatermelonDB-compatible store, src/sync/ implements the delta-sync client against the same GET /api/v1/projects/{pk}/sync/ endpoint described in Offline-first sync protocol, and src/auth/, src/api/, src/features/, and src/navigation/ mirror the web app’s layering. Android phones are the primary reference design, Android tablets second, iPhone deferred to 1.0 GA.
packages/mcp
Section titled “packages/mcp”A read-only Model Context Protocol server (trueppm-mcp), Apache 2.0, with its own pyproject.toml and Dockerfile — a pure HTTP client of the TruePPM REST API with no Django dependency and no direct database access. It ships the read-tool surface designed in ADR-0186 (stdio transport by default; --transport http for network use). See Computed, not guessed above for how it fits the AI-native foundation, and MCP server for the feature-level documentation.
packages/web
Section titled “packages/web”React 19 + TypeScript + Vite 8. Tailwind CSS with Design System v2.0 (navy/sage) tokens (WCAG 2.1 AA). TanStack Query for server state, Zustand for client state, React Router v8. The Schedule view (Gantt-style) uses a purpose-built canvas renderer in src/features/schedule/engine/ (no third-party Gantt library). The application shell, Schedule, Board, Sprints, and supporting views are wired against the live API.
packages/api
Section titled “packages/api”Django 5.2 + DRF 3.15. Django Channels 4 (ASGI). Celery 5.4 + Valkey (BSD-licensed Redis fork; wire-compatible). django-allauth + simplejwt. drf-spectacular (OpenAPI 3.0.3). PostgreSQL 16 with ltree for WBS hierarchy.
Valkey serves three distinct roles, each on its own logical database index off the same REDIS_URL (settings/base.py): the Celery broker/result backend (/0), the Django Channels layer backing WebSocket fan-out (/1), and — the role most easily missed — the Django CACHES["default"] backend (/2), which holds short-lived OIDC/OAuth2 state (the PKCE verifier, nonce, and exact redirect_uri for an in-flight SSO login — see apps/sso/services.py) and backs every DRF scoped throttle counter (login, refresh, Monte Carlo, and the general anon/user rate limits).
packages/website
Section titled “packages/website”This Astro Starlight site. Built with npx astro build; deploys to GitLab Pages.
packages/helm
Section titled “packages/helm”Helm 3 chart with vendored first-party sub-charts for PostgreSQL and Valkey (under packages/helm/charts/, using the official postgres and valkey/valkey images). Separate values-dev.yaml and values-prod.yaml overlays.
OSS / Enterprise boundary
Section titled “OSS / Enterprise boundary”The community edition must never import from trueppm_enterprise. The dependency is strictly one-way: enterprise → core.
# Verify the boundary is cleangrep -r "trueppm_enterprise" packages/# must return zero resultsCommunity: scheduling engine, CPM, Monte Carlo, Schedule (Gantt-style) UI, Board, Sprints workspace, program management (coordinating multiple projects within a program), baseline comparison, offline sync, real-time, 5-role RBAC, REST/WS API, Helm chart, MS Project import/export. On the Community roadmap but not yet shipped: basic single sign-on (OIDC/OAuth login against your own identity provider), time tracking with a weekly timesheet, and in-app baseline capture all land in 0.4; the installable PWA lands in 0.5 and the native Android app in 0.6.
Enterprise (separate repo): portfolio analytics and health scores, cross-program resource leveling, org identity governance (SAML 2.0 federation, SCIM provisioning, LDAP/AD directory sync, enforced org-wide SSO), immutable audit trail, custom roles, approval workflows, the org-wide Jira/GitLab/ServiceNow integration hub, AI scheduling, scenario modeling, multi-tenancy.
The OSS unit is the program (one PM, one or more related projects). The Enterprise unit is the portfolio (multiple programs under organizational governance).