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.
This commit is contained in:
2026-07-18 22:55:08 -07:00
parent 25d7a522cc
commit 05b28cb584
190 changed files with 2428 additions and 3337 deletions

View File

@@ -1,13 +1,13 @@
# Hugo static sites in Castle
# Hugo static sites in Wild PC
> **This is a stack — creation-time guidance for writing _new_ sites.**
> A stack is a template + conventions, not a runtime requirement. `castle program
> A stack is a template + conventions, not a runtime requirement. `wildpc program
> create --stack hugo` scaffolds from it (via Hugo's own `hugo new site`) and seeds
> the program's default build verb. An existing Hugo site adopted with `castle
> the program's default build verb. An existing Hugo site adopted with `wildpc
> program add` doesn't need this stack — it declares its own `commands:` /
> `build:`. See @docs/registry.md for `commands:`, `stack:` (optional), and `repo:`.
How to build, serve, and manage [Hugo](https://gohugo.io) sites as castle programs.
How to build, serve, and manage [Hugo](https://gohugo.io) sites as wildpc programs.
## Stack
@@ -18,27 +18,27 @@ How to build, serve, and manage [Hugo](https://gohugo.io) sites as castle progra
`/posts/index.html`) and 404s missing paths. (The default `spa: true` is a
single-page-app fallback that serves the root `index.html` for every unmatched
path — correct for React/Vite, but it swallows a Hugo site's in-page links back
to the homepage. `castle program create --stack hugo` sets `spa: false` for you.)
to the homepage. `wildpc program create --stack hugo` sets `spa: false` for you.)
- Package manager (only if a theme needs an asset pipeline): pnpm
Hugo has one meaningful dev verb — **build**. It has no native lint/test/type-check,
so the stack advertises only `build` / `install` / `uninstall`; `castle check` and
so the stack advertises only `build` / `install` / `uninstall`; `wildpc check` and
friends aren't offered (a site can still declare its own, e.g. an HTML linter, under
`commands:` — a declared verb always wins over the stack).
## Create a new site
```bash
castle program create my-site --stack hugo --description "My site"
wildpc program create my-site --stack hugo --description "My site"
cd /data/repos/my-site
castle program build my-site # hugo --gc --minify -> public/
castle apply my-site # serve at my-site.<gateway.domain>
wildpc program build my-site # hugo --gc --minify -> public/
wildpc apply my-site # serve at my-site.<gateway.domain>
```
The scaffold delegates the canonical skeleton to `hugo new site` (archetypes/,
content/, layouts/, static/, themes/, hugo.toml) and overlays the pieces a bare
skeleton lacks: minimal `layouts/` so the site **builds and serves without a
theme**, an example `content/posts/hello.md`, a castle-flavored `hugo.toml`
theme**, an example `content/posts/hello.md`, a wildpc-flavored `hugo.toml`
(`baseURL = "/"`, so assets resolve at the root of the site's own subdomain), and a
`.gitignore` for the regenerated `public/` and `resources/`.
@@ -78,14 +78,14 @@ cd themes/<theme> && pnpm install
## Deployment shape
`castle program create --stack hugo` writes:
`wildpc program create --stack hugo` writes:
- **`programs/<name>.yaml`** — `source`, `stack: hugo`, `build.outputs: [public]`.
- **`deployments/statics/<name>.yaml`** — `manager: caddy`, `root: public`,
`reach: internal` (flip to `public` to also expose over the tunnel).
The gateway serves `<source>/public` in place — no copy, no Node/Hugo process at
runtime. `castle program build` regenerates `public/`; `castle apply` renders the
runtime. `wildpc program build` regenerates `public/`; `wildpc apply` renders the
route and reloads the gateway.
## Adopting an existing Hugo site
@@ -93,7 +93,7 @@ route and reloads the gateway.
No stack needed — adopt the repo and declare how it builds:
```bash
castle program add /path/to/site --name my-site
wildpc program add /path/to/site --name my-site
```
Then set `build.commands` (as above) and add a `manager: caddy` deployment. This is

View File

@@ -1,9 +1,9 @@
# Python Tools in Castle
# Python Tools in Wild PC
> **This is a stack — creation-time guidance for writing _new_ CLI tools.**
> A stack is a template + conventions, not a runtime requirement. `castle program create
> A stack is a template + conventions, not a runtime requirement. `wildpc program create
> --stack python-cli` scaffolds from it and seeds the program's default dev-verb
> commands. An existing CLI adopted with `castle program add` doesn't need this stack — it
> commands. An existing CLI adopted with `wildpc program add` doesn't need this stack — it
> declares its own `commands:`. See @docs/registry.md for `commands:`,
> `stack:` (optional), and `repo:`.
@@ -49,11 +49,11 @@ Examples: `code/pdf2md/`, `code/gpt/`, `code/protonmail/`
## Creating a new tool
```bash
castle program create my-tool --stack python-cli --description "Does something"
wildpc program create my-tool --stack python-cli --description "Does something"
cd /data/repos/my-tool && uv sync
```
This scaffolds the project and registers it in `castle.yaml`.
This scaffolds the project and registers it in `wildpc.yaml`.
## pyproject.toml

View File

@@ -1,15 +1,15 @@
# Web APIs in Castle
# Web APIs in Wild PC
> **This is a stack — creation-time guidance for writing _new_ FastAPI services.**
> A stack is a template + conventions, not a runtime requirement. `castle program create
> A stack is a template + conventions, not a runtime requirement. `wildpc program create
> --stack python-fastapi` scaffolds from it and seeds the program's default
> dev-verb commands. An existing service adopted with `castle program add` doesn't need
> dev-verb commands. An existing service adopted with `wildpc program add` doesn't need
> this stack — it declares its own `commands:`. See @docs/registry.md
> for `commands:`, `stack:` (optional), and `repo:`.
How to build Python web APIs as castle service components. Based on the
How to build Python web APIs as wildpc service components. Based on the
patterns used in [wild-cloud/api](https://github.com/civilsociety-dev/wild-cloud)
and existing castle services (central-context, notification-bridge, event-bus).
and existing wildpc services (central-context, notification-bridge, event-bus).
## Stack
@@ -103,7 +103,7 @@ class Settings(BaseSettings):
settings = Settings()
```
Castle passes config via env vars in the deployment's `defaults.env`:
Wild PC passes config via env vars in the deployment's `defaults.env`:
```yaml
# programs/my-service.yaml
@@ -127,9 +127,9 @@ manage:
systemd: {}
```
The env a service runs with is exactly what's in `defaults.env`castle injects
The env a service runs with is exactly what's in `defaults.env`wildpc injects
nothing implicitly. Map the vars your settings read (above, `env_prefix:
"MY_SERVICE_"``MY_SERVICE_PORT`/`MY_SERVICE_DATA_DIR`) to castle's computed
"MY_SERVICE_"``MY_SERVICE_PORT`/`MY_SERVICE_DATA_DIR`) to wildpc's computed
values with the `${port}`/`${data_dir}` placeholders — add to
`deployments/my-service.yaml`:
@@ -137,11 +137,11 @@ values with the `${port}`/`${data_dir}` placeholders — add to
defaults:
env:
MY_SERVICE_PORT: ${port} # = expose.http.internal.port
MY_SERVICE_DATA_DIR: ${data_dir} # = $CASTLE_DATA_DIR/my-service
MY_SERVICE_DATA_DIR: ${data_dir} # = $WILDPC_DATA_DIR/my-service
CENTRAL_CONTEXT_URL: http://localhost:9001
```
`castle program create` scaffolds the `${port}`/`${data_dir}` lines for you.
`wildpc program create` scaffolds the `${port}`/`${data_dir}` lines for you.
## Application entry point
@@ -300,10 +300,10 @@ Mapping convention:
## Storage
Castle services use filesystem storage with JSON metadata sidecars:
Wild PC services use filesystem storage with JSON metadata sidecars:
```
$CASTLE_DATA_DIR/my-service/ # default /data/castle/my-service/
$WILDPC_DATA_DIR/my-service/ # default /data/wildpc/my-service/
└── bucket/
├── item-name
└── item-name.meta.json
@@ -447,11 +447,11 @@ uv run ruff format . # Format
## Scaffolding
`castle program create` generates all of this automatically:
`wildpc program create` generates all of this automatically:
```bash
castle program create my-service --stack python-fastapi --description "Does something useful"
wildpc program create my-service --stack python-fastapi --description "Does something useful"
```
See @docs/registry.md for manifest fields, castle.yaml structure,
and the full service lifecycle (`castle apply`, logs).
See @docs/registry.md for manifest fields, wildpc.yaml structure,
and the full service lifecycle (`wildpc apply`, logs).

View File

@@ -1,13 +1,13 @@
# Web Frontends in Castle
# Web Frontends in Wild PC
> **This is a stack — creation-time guidance for writing _new_ frontends.**
> A stack is a template + conventions, not a runtime requirement. `castle program create
> A stack is a template + conventions, not a runtime requirement. `wildpc program create
> --stack react-vite` scaffolds from it and seeds the program's default dev-verb
> commands. An existing frontend adopted with `castle program add` doesn't need this
> commands. An existing frontend adopted with `wildpc program add` doesn't need this
> stack — it declares its own `commands:`. See @docs/registry.md for
> `commands:`, `stack:` (optional), and `repo:`.
How to build, serve, and manage web frontends as castle programs.
How to build, serve, and manage web frontends as wildpc programs.
## Stack
@@ -56,7 +56,7 @@ import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
export default defineConfig({
// Castle sets VITE_BASE=/ at build time (every frontend serves at its
// Wild PC sets VITE_BASE=/ at build time (every frontend serves at its
// subdomain root). Reading it here keeps the bundle.s absolute asset
// URLs resolving at the root — no hand-tuning.
base: process.env.VITE_BASE ?? "/",
@@ -70,7 +70,7 @@ export default defineConfig({
```
> **Serving behind the gateway.** A static frontend is served at its own subdomain
> — `<name>.<gateway.domain>` — rooted at `/`, so `VITE_BASE` is always `/`. Castle's
> — `<name>.<gateway.domain>` — rooted at `/`, so `VITE_BASE` is always `/`. Wild PC's
> `react-vite` build passes `VITE_BASE=/`, and a `vite.config` that reads it (above)
> works unchanged. (Frontends are no longer mounted under a `/<name>/` path.)
@@ -130,19 +130,19 @@ project root:
24.14.1
```
Castle reads this pin and puts the matching node on PATH whenever it runs the
program's node — at build time (`castle program build`, incl. builds triggered from
the castle web app, which run inside the `castle-api` service) and at run time (a
Wild PC reads this pin and puts the matching node on PATH whenever it runs the
program's node — at build time (`wildpc program build`, incl. builds triggered from
the wildpc web app, which run inside the `wildpc-api` service) and at run time (a
`launcher: node` service's systemd unit). This is why a build works from the web app
even though the service's default PATH deliberately omits nvm's versioned dirs.
The pin is standard, tool-agnostic config — the program stays castle-independent; nvm
(and editors/CI) honor the same file. Castle resolves it against
`~/.nvm/versions/node` (override with `CASTLE_NODE_VERSIONS_DIR`), newest match wins.
The pin is standard, tool-agnostic config — the program stays wildpc-independent; nvm
(and editors/CI) honor the same file. Wild PC resolves it against
`~/.nvm/versions/node` (override with `WILDPC_NODE_VERSIONS_DIR`), newest match wins.
An exact pin (`24.14.1`) matches exactly; a partial (`24`) or a range (`>=24`) matches
the newest installed major. If the pinned version isn't installed, the verb fails loud
with a `nvm install <version>` hint instead of a cryptic `node: not found`. Unpinned →
Castle injects no node (it uses whatever ambient node is on PATH, and does not guess).
Wild PC injects no node (it uses whatever ambient node is on PATH, and does not guess).
## Registering as a program
@@ -194,19 +194,19 @@ See @docs/registry.md for the full registry reference.
## Serving with Caddy
For production, the static build output is served by Caddy rather than a Node
process. You do **not** write this block by hand — `castle apply` generates it.
process. You do **not** write this block by hand — `wildpc apply` generates it.
The flow:
1. You build the frontend with `castle program build <name>` (deploy does **not**
1. You build the frontend with `wildpc program build <name>` (deploy does **not**
build — it only points Caddy at `dist/`).
2. The Caddyfile generator emits a route per `manager: caddy` deployment (kind
**static**), rooted **in place** at `<source>/<root>` — no copy. Each static
frontend is served at its own subdomain `<name>.<gateway.domain>` (the
dashboard, `castle`, is also the target of the `:9000` redirect).
dashboard, `wildpc`, is also the target of the `:9000` redirect).
The build is run with `VITE_BASE=/`, so a `vite.config` that reads it (see
[Vite config](#vite-config)) emits root-relative asset URLs. The generated block
(in `~/.castle/artifacts/specs/Caddyfile`) is a host matcher inside the
(in `~/.wildpc/artifacts/specs/Caddyfile`) is a host matcher inside the
`*.<domain>` site:
```caddyfile
@@ -225,7 +225,7 @@ not hand-configured.
## API integration
Frontends talk to castle services via environment variables injected at build
Frontends talk to wildpc services via environment variables injected at build
time. Vite exposes variables prefixed with `VITE_`:
```bash
@@ -256,7 +256,7 @@ class ApiClient {
export const apiClient = new ApiClient()
```
When served behind the castle gateway, the API base URL can use the gateway's
When served behind the wildpc gateway, the API base URL can use the gateway's
proxy paths (e.g., `/central-context/`) instead of direct ports, avoiding CORS.
## React Query setup

View File

@@ -1,13 +1,13 @@
# Supabase Apps in Castle
# Supabase Apps in Wild PC
> **This is a stack — creation-time guidance for writing _new_ database-backed
> web apps.** A stack is a template + conventions, not a runtime requirement.
> `castle program create --stack supabase` scaffolds from it and seeds the
> `wildpc program create --stack supabase` scaffolds from it and seeds the
> program's default dev-verb commands. See @docs/registry.md for `commands:`,
> `stack:` (optional), and the deployment `manager` (and derived `kind`).
How to build tiny, database-backed web apps as castle programs that target a
**shared Supabase substrate**. This is Castle's "a stack whose default is a
How to build tiny, database-backed web apps as wildpc programs that target a
**shared Supabase substrate**. This is Wild PC's "a stack whose default is a
substrate": the app owns its code (and stays repo-durable), and rents the boring
backend — Postgres + auth + storage + RLS — that an app can't reliably reinvent.
@@ -16,7 +16,7 @@ backend — Postgres + auth + storage + RLS — that an app can't reliably reinv
Unlike the other stacks (which scaffold a self-contained process), a supabase app
is **code + migrations that deploy against a shared backend**:
- **The substrate** is one castle service (`supabase`, the `supabase-substrate`
- **The substrate** is one wildpc service (`supabase`, the `supabase-substrate`
repo) running self-hosted Supabase via a `manager: systemd` deployment with the
`compose` launcher. It is shared by
every supabase app. Stand it up once (see that repo's README).
@@ -27,10 +27,10 @@ is **code + migrations that deploy against a shared backend**:
Apps are isolated on the shared instance by their **own Postgres schema + RLS**
(and Storage buckets), under **one identity pool** — correct for a single-operator
datalake. Each app owns a schema named after the program (`my-app` → schema
`my_app`); `castle program build` creates and grants it, tracks migrations in a
per-app `<schema>.schema_migrations`, and PostgREST exposes it (castle derives the
`my_app`); `wildpc program build` creates and grants it, tracks migrations in a
per-app `<schema>.schema_migrations`, and PostgREST exposes it (wildpc derives the
substrate's `PGRST_DB_SCHEMAS` from the registered apps). This gives a clean
teardown — `castle delete --purge-data` runs `drop schema <app> cascade` — and
teardown — `wildpc delete --purge-data` runs `drop schema <app> cascade` — and
means migration version tokens never collide across apps. Substrate-per-app is
deliberately not supported: ~14 containers per app doesn't scale to "lots of small
ideas," and a DB-backed app is a pet either way.
@@ -69,14 +69,14 @@ gateway serves `public/` in place at `<name>.<gateway.domain>` (its own subdomai
```yaml
name: my-app
substrate: supabase # the shared castle service this app deploys against
substrate: supabase # the shared wildpc service this app deploys against
auth: public # public | private | shared: [handles]
schema: my_app # this app's isolated Postgres schema (frontend: db.schema)
```
## Migrations
`migrations/*.sql` are **numbered, forward-only, and idempotent**. `castle program
`migrations/*.sql` are **numbered, forward-only, and idempotent**. `wildpc program
build my-app` runs the versioned migration runner: it creates + grants the app's
schema, ensures a per-app `<schema>.schema_migrations` table, reads applied
versions, and applies only the **unapplied** files (in filename order) with
@@ -112,14 +112,14 @@ const db = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { db: { schema: SCHEMA
### Teardown
An app's rows live only on the substrate, so an ordinary `castle delete my-app`
An app's rows live only on the substrate, so an ordinary `wildpc delete my-app`
leaves the schema intact (and says so). To destroy the data too:
```bash
castle delete my-app --purge-data # drop schema my_app cascade
wildpc delete my-app --purge-data # drop schema my_app cascade
```
`castle apply` then prunes the schema from `PGRST_DB_SCHEMAS`; **restart the
`wildpc apply` then prunes the schema from `PGRST_DB_SCHEMAS`; **restart the
`supabase` service** for PostgREST to pick up the added/removed schema list.
## Auth, RLS & the three privacy layers
@@ -158,17 +158,17 @@ service itself is likewise at `supabase.<gateway.domain>`.) See
## Commands
```bash
castle program create my-app --stack supabase --description "..." # scaffold + register
castle program build my-app # apply unapplied migrations to the substrate
castle program test my-app # deno test over functions/ (if deno present)
castle apply # serve the static UI at <name>.<gateway.domain>
wildpc program create my-app --stack supabase --description "..." # scaffold + register
wildpc program build my-app # apply unapplied migrations to the substrate
wildpc program test my-app # deno test over functions/ (if deno present)
wildpc apply # serve the static UI at <name>.<gateway.domain>
```
## Scaffolding
`castle program create --stack supabase` generates the full layout above and
`wildpc program create --stack supabase` generates the full layout above and
registers the program as a static frontend. Set the anon key in `public/config.js`
(`cat ~/.castle/secrets/SUPABASE_ANON_KEY`), edit your migrations, and build.
(`cat ~/.wildpc/secrets/SUPABASE_ANON_KEY`), edit your migrations, and build.
See @docs/registry.md for the `compose` launcher, the substrate deployment definition,
and the full registry reference. The substrate itself lives in the