Files
wild-pc/docs/relationships.md
Paul Payne 05b28cb584 Rename Castle -> Wild PC across the repo
Repo-side rename only (Phases 1-3 of the migration plan); the live box
(~/.castle, systemd units, /data/castle, domains) is a separate cutover.

- Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api),
  dist names, entry point `wildpc = wildpc_cli.main:main`.
- Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*.
- Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml ->
  wildpc.yaml, /data/castle -> /data/wildpc.
- Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc.
- Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend.
- Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated;
  redundant nested uv.lock files dropped (workspace root lock is authoritative).

Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds.
Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
2026-07-18 22:55:08 -07:00

5.0 KiB

Relationships: requires, repos, and derived predicates

How wildpc models the relationships between programs, deployments, and repos — and answers questions like "is this functional?", "is it fresh?", "is it deployed?" — with the smallest possible amount of stored state.

The governing principle

Predicates are always derived. Encode only what is not derivable.

A predicate is a question we ask about a program or deployment: functional?, fresh?, deployed?. None of these are ever stored — each is a function over data wildpc already has (git, config, the registry). When a predicate can't be answered from derived data, find the one missing datum and ask: is it about the thing (a node property) or about a relationship (an edge property)? Encode only that datum. Everything else stays computed.

This is the same instinct as kind (derived from manager) — we don't store what we can compute, and a relationship that proves real and stable in the derived graph is a candidate to promote into a first-class concept. Diagnostic → evidence → abstraction, in that order.

Entities

  • program — the software catalog entry.
  • deployment — a program realized on this node (kind derived from manager).
  • repo — a git working copy. Derived from git rev-parse --show-toplevel on each program's source; several programs sharing one toplevel is a monorepo. Never stored.

Encoded preconditions: requires + system_dependencies

Everything we were calling "substrate", "wiring", or "dependency" reduces to preconditions ("A must have B to be functional"), encoded on the layer each belongs to. The relationship model unifies them into one requirement set with a typed target; the kind fixes the meaning and the check:

kind source (where encoded) means checked by
deployment the deployment's requires another deployment must exist registry / config
system the program's system_dependencies the host package/binary must be installed which / dpkg

A deployment declares the deployments it depends on. kind defaults to deployment, so an entry is just a ref (+ optional bind):

# deployments/<kind>/astro.yaml
requires:
  - ref: astro-guru
  - ref: supabase
  - { ref: litellm, bind: LITELLM_URL }   # bind: project the target's URL into env

A program's host-package preconditions stay on the program as system_dependencies (a plain list of package names); the model synthesizes the {kind: system} requirements from it for the functional? check. This split keeps each precondition on its natural layer — a deployment-ref is node-level wiring (belongs on the deployment), a host package is intrinsic to the software (belongs on the program). There is no requires on the program, and no kind: system written into a deployment's requires.

Only encode a requires edge that is not derivable and that wildpc itself must traverse for an operation (status, bring-up order, group ops). Do not duplicate what another layer already owns — systemd Requires=/After= for unit ordering, uv/pnpm for build graphs. This is wildpc's slice, uncoupled from any one package ecosystem.

Env is derived from requires, never scraped into it

Reading dependencies out of env strings is unstable (formats vary; a static frontend's API URL is baked into its bundle and invisible). The stable direction is the reverse: from an encoded {ref, bind} deployment requirement wildpc generates the wiring env — it knows the target's address (<ref>.<domain> / its port) and projects it into the consumer's env, optionally under the var named by bind. Same move as ${public_url}, one step further. Dependency → env, never env → dependency.

Derived predicates

Computed on demand from encoded requires + git + registry; nothing stored:

  • functional? — every requires is satisfied (system installed, deployment exists). The unmet ones are the node's status (doctor/status).
  • fresh? — the program's repo is at latest and clean (git status).
  • deployed? — the deployment is active in the registry.

New predicates are just new functions over the same preconditions — there is nothing to "unify" in storage.

What's encoded vs derived (the whole surface)

datum source stored?
repo / monorepo git toplevel derived
fresh? / deployed? / functional? git / registry / requires derived
env wiring for a dependency the requires edge + target address derived
fan-in ("widely depended-on") count of requires derived
a non-derivable requirement (frontend→backend, host package) encoded (requires)
the env var to bind a dep's URL to, when non-conventional encoded (requires[].bind)

Every irreducible found so far is an edge (a relationship); no new node property has been needed yet — a sign the encoded surface stays tiny.