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:

ComponentWhat It ContainsLoaded From
AppsEvery deployed app with its metadata , dependencies, secrets, and target assignmentservices/*/service.yml + roles/*/meta.yml
TargetsEvery target with its type, IP, node, resources, and computed app listnetwork.yml
SecretsWhich secrets each app declares (key names only — no decryption)App metadata
ProvidersWhich provider backends are configuredproject.yml
StateWhat PSW has done in the past (previous deployments, operations)state.yml
Computed relationshipsDependency edges, reverse dependencies, broadcast injection, active/absent setsComputed 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:

ValidatorWhat It Checks
Target referencesEvery app points to a target that exists in network.yml
Role existenceEvery active app has a matching roles/ directory
Target type matchApps are deployed to compatible target types (lxc vs bare)
Dependency graphNo circular dependencies (A needs B, B needs A)
Missing dependenciesAll required dependencies are installed and active
Secret collisionsNo two apps claim the same secret key
State consistencystate.yml matches what’s actually in services/ and network.yml
Hardware resolutionApps with GPU/USB requirements deploy to nodes that have the hardware
SSO consistencyApps declaring SSO conventions have the right secrets and config
Monitoring consistencyMonitoring conventions have valid auth config
Empty targetsWarns 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:

GuardrailWhat It Catches
Port conflictsTwo apps on the same target binding to the same port
Subdomain conflictsTwo 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 check

Fast, 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 validate

Layers 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 plan

How the Graph Is Used#

The project graph is the central data structure that feeds into nearly every PSW operation:

SystemWhat It Uses From the Graph
Convention engineApp metadata, targets, dependencies — to generate routing, SSO, monitoring files
Deploy engineActive apps, targets, metadata — to build deployment context and render templates
Deploy varsActive apps, targets, metadata — to resolve secrets and cross-app references
DNS reconciliationApp subdomains, provider backends — to push DNS records
Convergence differCompares graphs to detect changes between deploys
Remote accessExposed 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