Project Graph & Validation#
What Is the Project Graph?#
The project graph is a single, immutable snapshot of your entire user project — every app , target , secret , provider , and the relationships between them — loaded once and used everywhere.
Think of it like a photograph of your self-hosted solution’s configuration at a point in time. PSW takes this snapshot, validates it for problems, and then hands it to the systems that need it: the convention engine, the deployment pipeline, DNS reconciliation, and more.
Why Does It Exist?#
Your project is spread across many files: project.yml, network.yml, services/*/service.yml, roles/*/meta.yml, state.yml, and more. Each file is correct on its own, but problems hide in the relationships between them:
- An app references a target that doesn’t exist
- Two apps on the same target bind to the same port
- A dependency was removed but the app that needs it is still deployed
- An SSO convention is declared but the required secrets are missing
The project graph solves this by loading everything into a single structure where these cross-file relationships can be checked.
What’s In the Graph?#
The graph contains six major components:
| Component | What It Contains | Loaded From |
|---|---|---|
| Apps | Every deployed app with its metadata , dependencies, secrets, and target assignment | services/*/service.yml + roles/*/meta.yml |
| Targets | Every target with its type, IP, node, resources, and computed app list | network.yml |
| Secrets | Which secrets each app declares (key names only — no decryption) | App metadata |
| Providers | Which provider backends are configured | project.yml |
| State | What PSW has done in the past (previous deployments, operations) | state.yml |
| Computed relationships | Dependency edges, reverse dependencies, broadcast injection, active/absent sets | Computed from the above |
How It’s Built#
When PSW needs the graph (during psw app add, psw project check, psw project validate, psw project plan, or convergence
), it builds it in a single pass:
1. Load project config (project.yml)
2. Load targets from network.yml
3. Load apps: scan services/ for manifests, merge with roles/ metadata
4. Load provider backends from project.yml
5. Build dependency graph (which apps depend on which)
6. Analyze secrets (which apps declare which keys)
7. Load state.yml (if it exists)
8. Compute broadcast injection (add broadcast apps to all targets)
9. Compute convenience sets (active apps, exposed targets, etc.)Once built, the graph is frozen — nothing can modify it. This guarantees that every system reading the graph sees the same data.
Broadcast Injection#
Some apps are marked as broadcast in their metadata — they should run on every managed target , not just one. During graph construction, PSW automatically adds these apps to every target’s app list.
For example, Alloy
(log collector) and Node Exporter
(metrics exporter) are broadcast apps. You never have to list them in network.yml — the graph injects them everywhere.
Validation#
PSW has three layers of validation, each catching different kinds of problems:
Layer 1: Schema Validation#
“Are the individual files correct?”
Checks each YAML file against its expected structure. For example: does project.yml have a valid domain field? Does each service.yml have a name and target?
This catches typos, missing fields, and structural errors in individual files.
Layer 2: Graph Validation#
“Does the project work as a whole?”
After schema validation passes, PSW builds the project graph and runs a suite of validators that check cross-file relationships:
| Validator | What It Checks |
|---|---|
| Target references | Every app points to a target that exists in network.yml |
| Role existence | Every active app has a matching roles/ directory |
| Target type match | Apps are deployed to compatible target types (lxc vs bare) |
| Dependency graph | No circular dependencies (A needs B, B needs A) |
| Missing dependencies | All required dependencies are installed and active |
| Secret collisions | No two apps claim the same secret key |
| State consistency | state.yml matches what’s actually in services/ and network.yml |
| Hardware resolution | Apps with GPU/USB requirements deploy to nodes that have the hardware |
| SSO consistency | Apps declaring SSO conventions have the right secrets and config |
| Monitoring consistency | Monitoring conventions have valid auth config |
| Empty targets | Warns about targets with no apps assigned |
Each issue is classified as an error (blocks deployment) or a warning (informational).
Layer 3: Guardrails#
“Is deployment safe?”
Guardrails are specialized safety checks that catch problems graph validators don’t:
| Guardrail | What It Catches |
|---|---|
| Port conflicts | Two apps on the same target binding to the same port |
| Subdomain conflicts | Two apps claiming the same subdomain for Traefik routing |
These are critical — if a guardrail fails, deployment is blocked. There’s no way to override them because deploying with port or subdomain conflicts would break your apps.
Commands#
psw project check#
Runs guardrails only — a quick safety check before deployment:
psw -C ~/my-project project checkFast, focused on deployment blockers (port and subdomain conflicts).
psw project validate#
Runs all five validation layers — a comprehensive offline audit:
psw -C ~/my-project project validateLayers run in order, with early exit on errors:
Layer 1: Schema validation (individual files)
Layer 2: Cross-reference checks (services/ ↔ roles/)
Layer 3: Graph validation (all validators)
Layer 4: Guardrails (port/subdomain conflicts)
Layer 5: Convention validation (rendered files)psw project plan#
Uses the graph to preview what convergence would do — infrastructure changes, affected targets, service changes, secrets , wiring actions, aggregator syncs, and guardrail results — without actually deploying anything.
psw -C ~/my-project project planHow the Graph Is Used#
The project graph is the central data structure that feeds into nearly every PSW operation:
| System | What It Uses From the Graph |
|---|---|
| Convention engine | App metadata, targets, dependencies — to generate routing, SSO, monitoring files |
| Deploy engine | Active apps, targets, metadata — to build deployment context and render templates |
| Deploy vars | Active apps, targets, metadata — to resolve secrets and cross-app references |
| DNS reconciliation | App subdomains, provider backends — to push DNS records |
| Convergence differ | Compares graphs to detect changes between deploys |
| Remote access | Exposed targets — to validate tunnel configuration |
Key Principles#
- Built once, used everywhere — the graph is constructed a single time and shared across all systems
- Immutable — once built, nothing can change it, ensuring consistency
- Cross-file awareness — catches problems that file-level validation can’t see
- Fail early — validation runs before deployment, not during it
- Layered — simple checks run first, expensive checks only run if basics pass