Toilville LLC — Forge Reference
Reference for forge operators (Peter + future sysadmin). Covers infrastructure internals that teammates do not need to know.
See deploy/README.md for the full authoritative stack
list and layering.
Quick summary: - core.yml — always running: postgres,
redis, redpanda, openbao - ritual.yml — always running:
ritual-server, ritual-consumer, ceremony-warden -
platform.yml — always running: keycloak, forgejo, matrix -
observability.yml — always running: grafana, prometheus,
dozzle - apps.yml — always running: forge-bridge,
slack-bridge, audit-events, lore-drift, git-poller
Legacy stacks (docker-compose.yml,
docker-compose.prod-full.yml) are deprecated — do not use
for new deployments.
bin/forge-deploy-check # report drift between tracked files and live host
bin/forge-deploy-check --sync # apply tracked files to host| Context | Broker | Protocol |
|---|---|---|
| External/TLS clients (daemons, scripts, hooks) | spel.toilville.dev:19092 |
SASL_SSL |
Internal Docker containers (on forge-network) |
redpanda:9092 |
plaintext |
Never use 10.10.0.1:19092 for TLS
clients. The Redpanda TLS cert is issued for
spel.toilville.dev. IP addresses break hostname
validation.
The host firewall must also allow inbound 19092/tcp on
toilville-forge. That rule is persisted via
netfilter-persistent and is required for the public Kafka
listener to survive reboots.
SASL credentials: secret/forge/kafka-scram in
OpenBao.
Validation:
bin/forge-kafka-check # fail if any TLS client uses an IP brokerhttp://172.22.0.1:8200http://10.10.0.1:8200https://vault.toilville.dev (via nginx
proxy → 10.10.0.1)openbao on
toilville-forgeThe canonical mapping of service → vault path is in
forge-manifest.spel under
infrastructure.secret_paths. Key paths:
| Service | Vault Path |
|---|---|
| kafka SCRAM credentials | secret/forge/kafka-scram |
| ritual-server | secret/forge/ritual-server |
| audit-events | secret/forge/audit-events |
| lore-guards | secret/forge/lore-guards |
| forge-bridge | secret/forge/forge-bridge |
| git-poller | secret/forge/git-poller |
| Woodpecker CI | secret/forge/woodpecker |
Each service has a sidecar in
deploy/vault-agent/<service>/: -
agent.hcl — AppRole auth + template sink -
secrets.env.tpl — renders secrets to
/vault-secrets/secrets.env
Vault address in sidecars is http://172.22.0.1:8200
(Docker gateway IP).
Claude hooks are registered in ~/.claude/settings.json:
- PreToolUse: bin/claude_pre_tool — Bastia
grant gate + audit event - UserPromptSubmit:
generated/hooks/claude_pre_prompt.sh — context injection -
SessionStart:
generated/hooks/claude_session_init.sh — env injection +
permission sync
The generated/hooks/ files are generated from
bin/templates/hook_wrapper.sh.tmpl. Do not edit generated
files by hand — edit the template and regenerate:
bin/forge-regen-hooks
git add generated/hooks/
git commit -m "regen: hooks from canonical template"All hooks fail open (exit 0) by default. The only blocking behavior
is the Bastia grant gate in claude_pre_tool for Z2/Z3
operations (vault writes, psql mutations, remote writes, paladin frame
ops, ssh/scp to production).
Local-only mode (set at session init via
RITUAL_LOCAL_FIRST=1): - Pre-prompt hook skips Kafka/server
and serves from cr-sqlite cache - Cache freshness is emitted to stderr:
[forge-local] cache age: Xs, tier: N
Managed in core.bastia_grant_scopes and
core.bastia_grant_policies.
| Scope | Policy | Auto-Approve | Max TTL |
|---|---|---|---|
vault:read |
bastia-grant-vault-read | Yes | 60s |
vault:write |
bastia-grant-vault-write | No (SLA) | 60s |
db:query |
bastia-grant-db-query | Yes | 120s |
paladin:exec |
bastia-grant-paladin-exec | No (SLA) | 120s |
remote:write |
bastia-grant-remote-write | No (SLA) | 60s |
service:restart |
bastia-grant-service-restart | No (SLA) | 60s |
secret:patch |
bastia-grant-secret-patch | No (SLA) | 60s |
forgejo:credential-rotate |
bastia-grant-forgejo-credential-rotate | Yes | 120s |
kafka:delegated |
bastia-grant-kafka-delegated | — | 900s |
Narrow helpers in bin/forge-privileged-op wrap the
common operations. Do not add raw bao, ssh, or
scp to Claude’s allowlist — use bounded helpers
instead.
bin/forge-privileged-op vault-read <path>
bin/forge-privileged-op vault-write <path> <json>
bin/forge-privileged-op remote-copy <local> <host:path>
bin/forge-privileged-op remote-restart <service>
bin/forge-privileged-op service-restart <service>
bin/forge-privileged-op secret-patch <path> <field> <value>
bin/forge-privileged-op db-exec <sql-file>
bin/forge-privileged-op rotate-forge-ci-passwordEach operation: requests grant → executes → revokes. Fail-closed if Bastia denies.
cr-sqlite cache lives at ~/.ritual/schema.db. Freshness
tiers:
| Tier | Condition | Behavior |
|---|---|---|
| 0 | < 5 min old | Fresh — Kafka not needed for reads |
| 1 | 5 min – 1 hr | Stale — reads may lag |
| 2 | > 1 hr | Very stale — consider sync |
| 3 | Missing | No local cache — server required |
bin/forge-local-cache-check # human-readable report
bin/forge-local-cache-check --json # machine-readablebin/ritual-system-doctor # run all checks
bin/forge-kafka-check # Kafka policy validation
bin/forge-deploy-check # deploy drift check
bin/forge-local-cache-check # cache freshnessYou don’t need to learn Spelwork, ritual, Kafka, or Docker to contribute. This guide explains the human workflows.
Send an email to the Toilville forge inbox. Write your request in plain English in the subject line. The forge reads it, creates a work item, and routes it for review.
Examples of things you can send: - Review the latest design
mockup
- Can you approve the Q2 budget estimate?
- Update
the sprint goal for this week
- Help — the integration is broken
again
You’ll receive a reply if clarification is needed. Decisions and outcomes are recorded automatically.
Open a pull request or issue in the Forgejo
repository at git.toilville.dev. The forge monitors all
repos in the toilville organization and creates work items
automatically when you:
You don’t need to mention the forge or use any special syntax. Just work normally in git.
When a request requires a group decision, you may receive a decision parlay — a structured prompt asking you to vote, object, or add context.
What did we decide about X?
If you need something restarted, redeployed, or updated on the forge infrastructure, send an email or open an issue describing what you need and why. The request is routed for approval before anything runs.
Do not ask for raw access to production servers. Bounded operation requests are the correct path.
These are implementation details — you do not need to understand them to contribute:
If you find yourself needing these, ask an operator — it likely means there’s a missing bounded helper or form that should exist.
Send an email with your question. Or open a Forgejo issue in
toilville/spelwork tagged help.
No services registered.
No migrations recorded.
No events cataloged.
No intents cataloged.
No frames recorded.
No compliance rules configured.
.spel audit — 2026-05-08Two parallel Explore-agent passes over ~/.spel/.
Read-only; no fixes applied. Tracking wish:
481920bf-1128-4b8c-8479-67ca695b24dc.
| # | Finding | Impact | Effort |
|---|---|---|---|
| 1 | 5 stale agent worktrees in .claude/worktrees/ |
~1.1 GB reclaimable | S |
| 2 | spelwork-ap/src/age.rs creates ap.stewards
+ ap.guises at startup outside migrations |
Schema-first violation, critical-path | S |
| 3 | api-spec/dynseal/dynseal-pattern-publisher.py creates 3
tables (pattern_attestations,
information_provenance, supply_chain_log)
outside migrations |
Schema-first violation, runtime | M |
| 4 | deploy/compliance-schema.sql (compliance DB on
:5433) bypasses migration versioning |
Ambiguous — separate DB but unclear ownership | M |
| 5 | services/billing-ingest/ has no manifest (no
Cargo.toml/pyproject.toml/package.json) — possibly orphaned |
Unknown | S |
migrations/| File | Line | DDL | Severity |
|---|---|---|---|
services/spelwork-ap/src/age.rs |
56 | CREATE TABLE IF NOT EXISTS ap.stewards |
H — runtime |
services/spelwork-ap/src/age.rs |
74 | CREATE TABLE IF NOT EXISTS ap.guises |
H — runtime |
api-spec/dynseal/dynseal-pattern-publisher.py |
49 | CREATE TABLE IF NOT EXISTS pattern_attestations |
M |
api-spec/dynseal/dynseal-pattern-publisher.py |
63 | CREATE TABLE IF NOT EXISTS information_provenance |
M |
api-spec/dynseal/dynseal-pattern-publisher.py |
78 | CREATE TABLE IF NOT EXISTS supply_chain_log |
M |
services/ritual-consumer/main.py |
80 | CREATE TABLE IF NOT EXISTS retry_queue |
S — local SQLite resilience buffer; OK to keep |
forge-kv-cache/src/store/cold.rs |
68 | CREATE TABLE IF NOT EXISTS blocks |
S — embedded SQLite cache; OK to keep — now external:
git.toilville.dev/Toilville/forge-kv-cache |
Recommendation: Migrate items 1–5 to versioned migrations. Items 6–7 are intentional embedded-SQLite isolation patterns.
233 total references across services/bridges/adapters:
| Table | File count | Hot spots |
|---|---|---|
core.frames |
46 | winnow-worker, forge-api, forge-bridge, imap-bridge, matrix-bridge |
core.external_sync_state |
24 | forge-bridge (12), imap-bridge (5), matrix-bridge (3) |
core.actors |
18 | winnow-worker, forge-api, bridges/* |
core.lore |
16 | forge-api, forge-bridge, imap-bridge |
core.lineage_events |
14 | winnow-worker (3), forge-api (7), bridges/* (4) |
core.inbox_items |
10 | forge-api (6), imap-bridge (2), matrix-bridge (2) |
core.embedding_queue |
7 | forge-embeddings (7) |
core.winnow_batch_work_orders |
5 | winnow-worker (5) |
core.ritual_plans |
5 | winnow-worker (5) |
ap.stewards / ap.guises |
5 each | spelwork-ap (5 each) |
Pragmatic note: 233 string literals across ~14
services is high but typical for a polyrepo. A constants module would
centralize but the current pattern is workable. Flag for
enforcement: a pre-commit hook that warns on
CREATE TABLE outside migrations/ would catch
the violations above without forcing a constants refactor.
| File | Issue |
|---|---|
services/forge-api/routers/wishes.py:45-47 |
["pending", "in_progress", "blocked", "failed", "completed"]
— should source from manifest pattern catalog |
services/forge-bridge/scripts/backfill_forgejo_issues.py:72 |
frame_status = "pending" if state == "open" else "completed" |
services/forge-bridge/tests/test_frame_upsert.py:21-36 |
STATE_MAP, FRAME_STATUS_MAP test fixtures
with hardcoded transformations |
.sql files outside
migrations/| File | Size | Verdict |
|---|---|---|
services/flink-jobs/wish_execution_join.sql |
4.4K | Flink streaming SQL (Kafka virtual tables) — OK, not DB DDL |
services/flink-jobs/wish_plan_executor.sql |
— | Flink streaming SQL — OK |
services/flink-jobs/swarm_phase_gate.sql |
— | Flink streaming SQL — OK |
deploy/compliance-schema.sql |
7K | Compliance DB schema (separate PG :5433) — needs canonical-source clarification |
deploy/postgres/compliance-init.sql |
— | Compliance DB init — same |
core/core/bastia_manifest_validation_handler.sql |
— | Validation utility |
core/core/tools/analyze_intent_misses.sql |
— | Debug query — OK |
/Users/peterswimm/.spel/.claude/worktrees/ — 6
worktrees, 1.2 GB total:
| Worktree | Size | Age | Uncommitted | Action |
|---|---|---|---|---|
agent-a1b642fe |
89M | 7d | none | delete |
agent-a24ae579 |
89M | 7d | none | delete |
agent-a4709fc8 |
89M | 7d | none | delete |
agent-a9739024 |
89M | 7d | 16K untracked — services/ritual-consumer/
(Dockerfile + main.py + requirements.txt) |
review before delete |
agent-afcdc9c5 |
776M | 7d | none | delete |
great-lamarr-7e972b |
90M | 6d | none | delete |
The agent-a9739024 untracked
ritual-consumer/main.py is a production-ready async Kafka
consumer differing from services/ritual-consumer/.
Inspect before discarding.
Cleanup: git worktree prune from ~/.spel/,
or targeted git worktree remove --force <path>.
| File | Size | Mtime | Action |
|---|---|---|---|
files.zip |
15K | 2026-04-18 | delete — no clear purpose, 3 weeks old |
SPELWork-Pitch-Deck.pptx |
157K | 2026-05-03 | keep — referenced in brand assets |
forge.db |
3.4M | 2026-05-07 | keep — active |
forge-manifest.spel |
15K | 2026-05-08 | keep — current |
billing-ingestservices/billing-ingest/ — only Dockerfile
+ ingest.py, no manifest file. No grep references outside
its own directory. Action: confirm via git log +
CLAUDE.md / CONTRACT.md whether active; if orphaned, archive or delete
(~200 KB).
target/ 589M (Rust).venv/ 1.1 GB (Python)logs/ 47M.pytest_cache/All in .gitignore. No tracked build files. No
action needed.
All services under services/ have git activity within
the past 2 weeks. No retirement candidates. Sampled
Cargo.toml and pyproject.toml files show no
obvious unused dependencies — workspace is well-curated.
agent-a9739024/services/ritual-consumer/ first; merge or
discard, then git worktree prune the rest.spelwork-ap AGE bootstrap DDL
to a new versioned migration
(e.g. migrations/3XX_ap_steward_topology.sql). Replace the
.rs CREATE TABLE with a schema-existence
assertion.dynseal provenance DDL the
same way — pattern_attestations,
information_provenance, supply_chain_log.CREATE TABLE /
ALTER TABLE outside migrations/ and
**/tests/. Cheap enforcement, prevents regression.deploy/compliance-schema.sql is canonical for the SOC2
audit DB, or whether it should move under migrations/ with
a schema-name prefix. Document the answer in
docs/src/compliance.md.services/forge-api/routers/wishes.py —
source the status enum from the manifest pattern catalog instead of
inline list literal. Optional, low-impact.services/billing-ingest/ —
confirm active or archive.files.zip — delete the orphan
archive.forge-kv-cache/cold.rs:68 (blocks table) —
embedded SQLite cache, intentional schema isolation. Now external:
git.toilville.dev/Toilville/forge-kv-cache.services/ritual-consumer/main.py:80
(retry_queue) — local SQLite resilience buffer when Kafka
unavailable.sql files (flink-jobs/*.sql) —
Kafka streaming virtual tables, not DB DDLcore/core/tools/*.sql — one-off debug queriesDockerfile copies / 6 main.py copies
across services — standard polyrepo pattern, not duplication.sql files outside migrations (3 OK Flink, 3
ambiguous compliance/utility)billing-ingest).spel/ size, of which 2.7 GB is
properly-ignored build cache + virtualenv + worktreesLore contributed by external repos via
POST /familiar/lore/ingest.
No cross-repo lore contributed yet.