Files
bvandeusenandClaude Opus 5.5 787685ac63
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 16s
CI & Build / TypeScript typecheck (push) Successful in 55s
CI & Build / integration (push) Successful in 1m9s
CI & Build / Python tests (push) Successful in 2m7s
CI & Build / Build & push image (push) Successful in 30s
fix(6244): the instance keeps its own SECRET_KEY in /data, and docker-compose.yml is the compose users deploy from
With no SECRET_KEY set, Grimoire fell back to a fixed placeholder. The
quickstart compose substituted change-me-in-production, so the README's
own update step (pull, up -d) recreated the container on that placeholder,
and every credential sealed under the key the user started with stopped
opening.

- config: with neither SECRET_KEY nor SECRET_KEY_FILE, the first start
  writes a random key to DATA_DIR/secret_key (mode 0600, published with a
  hard link so concurrent starts agree), and later starts read it back.
  An explicit key wins and leaves the file alone. A missing
  SECRET_KEY_FILE or an empty key file stops the start instead of
  switching keys. With no data directory (a run outside the container)
  the process gets a temporary key and says so.
- app: warns on a public placeholder key, on a temporary key, and when
  /data is not a mounted volume.
- Dockerfile creates /data.
- docker-compose.yml is now the deploy compose (published image, both
  volumes, nothing to set). The build-from-source compose is
  docker-compose.dev.yml, and docker-compose.quickstart.yml is gone. The
  Swarm stack file gains the /data volume and keeps its explicit key,
  because a Swarm can move the app away from a node-local volume.
- README: a Deploy section with the compose inline, SECRET_KEY optional,
  "keep both volumes", and updates that keep the key. A test holds the
  README's copy equal to the file.
- Installs that ran on the change-me-in-production placeholder get a key
  of their own on the new compose; Settings then asks once for saved
  forge tokens and the backup secret.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:13:43 -04:00

4.3 KiB

Development

Running it

docker compose -f docker-compose.dev.yml up --build       # app + PostgreSQL at http://localhost:5000
docker compose -f docker-compose.dev.yml up --build app   # rebuild after a change
docker compose -f docker-compose.dev.yml down -v          # stop and wipe the database

The image builds the frontend, so a frontend change needs a rebuild too. For quicker frontend work, run Vite against the running app; it proxies /api to port 5000:

cd frontend && npm ci && npm run dev   # http://localhost:5173

Checks

CI runs these on every push; locally they need uv sync --extra dev and cd frontend && npm ci.

make lint        # ruff check src/
make typecheck   # vue-tsc --noEmit
make test        # pytest tests/
make check       # all three

Unit tests run without a database. Tests marked integration need a real PostgreSQL and run in CI's integration job, against a schema built by the real migrations.

Database migrations

The container runs alembic upgrade head on every start, after waiting for the database to accept connections.

docker compose -f docker-compose.dev.yml exec app alembic revision -m "short_description"
  • Number revisions sequentially (0123_...). Revision ids must fit in 32 characters, the width of alembic_version.version_num.
  • Write both upgrade() and downgrade().
  • Adding a value to a CHECK-constrained column means dropping and re-adding the constraint in the same migration.
  • Keep migrations to schema changes. Filling a derived table from a large one belongs in a background backfill after startup, where its cost doesn't hold up the boot.
  • A migration that depends on application code (a key derivation, a serialiser) copies what it needs rather than importing it, so a later edit can't change what an old migration does.

The plugin

plugin/ ships straight from git: installs fetch it through .claude-plugin/marketplace.json, and there is no build step. After changing anything under plugin/ or .claude-plugin/, run make mint-plugin before committing. Claude Code only refreshes an installed plugin when its version changes, and CI fails a plugin change that didn't bump it.

The hooks must run on a bare machine: POSIX shell, awk, sed and the usual coreutils, plus git and curl. They fail open, so a slow or unreachable Grimoire never blocks Claude Code.

CI and releases

CI runs on Gitea Actions (workflow in .forgejo/workflows/ci.yml) in the shared ci-python:3.14 image (ci-requirements.md lists what it relies on). Six jobs: TypeScript typecheck, plugin hooks, Python lint (plus design-token and dangling-style checks), unit tests, integration tests, and the image build, which waits on all of them except the plugin job.

Push Image tags
dev :dev, :<sha>
main :latest, :<sha>
a v* tag :latest, :<version>, :<sha>

:<sha> never moves, so it is the rollback point. Images publish to git.fabledsword.com/bvandeusen/fabledgrimoire, with a registry layer cache at :cache. The build needs the REGISTRY_USER and REGISTRY_TOKEN (a token with write:packages) repository secrets.

Work happens on dev. A release merges dev into main with a plain merge commit; a dated vYYYY.MM.DD.HHMM tag, matching the version the image reports, can mark it.

Conventions

Backend

  • Services open their own session: async with async_session() as session: (from grimoire.models).
  • Routes and MCP tools stay thin; the logic lives in services/, shared by both.
  • Access checks go through services/access.py, never inline ownership checks.
  • Every wait on something outside the process has a deadline.

Frontend

  • API calls go through the typed helpers in frontend/src/api/client.ts.
  • Pinia stores for shared state, ref() for state local to a component; composables for reusable behaviour.
  • Colours, spacing and type come from the design system's tokens in theme.css, which is generated, so CI rejects an unknown var().

Commits

type(scope): what the change does

Why, and anything a reader tracing this change later needs.

Types: feat, fix, refactor, docs, chore, test. Comments in the code describe how it works today; the history of a change belongs in its commit message.