SPELWork API & Architecture Docs

Toilville LLC — Forge Reference

Forge Operator Guide

Reference for forge operators (Peter + future sysadmin). Covers infrastructure internals that teammates do not need to know.


Compose Stack Ownership

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.

Drift Check

bin/forge-deploy-check           # report drift between tracked files and live host
bin/forge-deploy-check --sync    # apply tracked files to host

Kafka Broker Policy

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 broker

Vault / OpenBao

Secret Paths

The 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

Vault-Agent Sidecars

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).


Hook System

Hook Lifecycle

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

Regenerating Hooks

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"

Hook Behavior

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


Bastia Grant Scopes

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.


Privilege Operations

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-password

Each operation: requests grant → executes → revokes. Fail-closed if Bastia denies.


Local Cache

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-readable

System Health Check

bin/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 freshness

Working with Toilville — Teammate Guide

You don’t need to learn Spelwork, ritual, Kafka, or Docker to contribute. This guide explains the human workflows.


How to submit a request or idea

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.


How to contribute code or documentation

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.


How decisions get made

When a request requires a group decision, you may receive a decision parlay — a structured prompt asking you to vote, object, or add context.


How to request a service operation

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.


What you’ll never need to know

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.


Getting help

Send an email with your question. Or open a Forgejo issue in toilville/spelwork tagged help.


Service Registry

No services registered.


Schema Migrations

No migrations recorded.


Event Catalog

No events cataloged.


Intent Catalog

No intents cataloged.


Forge Frames

No frames recorded.


Compliance

No compliance rules configured.


.spel audit — 2026-05-08

Two parallel Explore-agent passes over ~/.spel/. Read-only; no fixes applied. Tracking wish: 481920bf-1128-4b8c-8479-67ca695b24dc.


Top 5 high-impact findings

# 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

1. Hardcoded schemas (canonical violations only)

1a. SQL DDL outside 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.

1b. Top-10 most-repeated table-name string literals

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.

1c. Hardcoded enum strings (status / intent values)

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

1d. Stray .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

2. Dead code / orphans

2a. Agent worktrees — biggest cleanup win

/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>.

2b. Orphan top-level files

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

2c. Possibly-orphan service: billing-ingest

services/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).

2d. Build artifacts — properly gitignored

  • target/ 589M (Rust)
  • .venv/ 1.1 GB (Python)
  • logs/ 47M
  • .pytest_cache/

All in .gitignore. No tracked build files. No action needed.

2e. Services / dependencies — clean

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.


3. Recommendations — ranked

  1. Worktree cleanup (S, ~1.1 GB reclaimed). Inspect agent-a9739024/services/ritual-consumer/ first; merge or discard, then git worktree prune the rest.
  2. Migrate 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.
  3. Migrate dynseal provenance DDL the same way — pattern_attestations, information_provenance, supply_chain_log.
  4. Pre-commit hook: flag CREATE TABLE / ALTER TABLE outside migrations/ and **/tests/. Cheap enforcement, prevents regression.
  5. Compliance DB ownership: decide whether 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.
  6. services/forge-api/routers/wishes.py — source the status enum from the manifest pattern catalog instead of inline list literal. Optional, low-impact.
  7. Investigate services/billing-ingest/ — confirm active or archive.
  8. files.zip — delete the orphan archive.

4. Pragmatic exceptions (flagged but OK to leave)


5. Summary metrics


Cross-Repo Lore

Lore contributed by external repos via POST /familiar/lore/ingest.

No cross-repo lore contributed yet.