Upgrading Panella
An upgrade is a version bump — the image pin or the installed package version — plus a restart.
The one rule: back up first. Everything below assumes the compose topology in
SELF_HOST.md (a panella-store volume mounted read-only into the facade, plus a
panella-http-data volume for the facade's own token/audit/outbox state).
Baked embedding-model upgrade
Upgrading to this release switches ordinary boxes automatically to the baked embedding model from the same weights family. Any old Hugging Face cache in the named volume becomes an ignored, harmless orphan and can be cleaned up optionally. The numeric drift evidence is deferred to the C0-3b evidence run. Custom-model users are unaffected: they still download at runtime only when they use the documented explicit custom-model egress recipe.
Step 0 — Backup
panella backup --out ./panella-backup-$(date +%Y%m%d).tar.gz
This snapshots the store, the facade's token/audit/outbox DBs, and (if configured) the governance
overlay file into one hash-manifested archive. It validates the snapshot before packaging (the
store via store_probe, the audit log via its hash-chain verify) and refuses to write a backup it
can't already prove is coherent. Do this before touching the image pin or the package version —
if anything below goes wrong, Restore from backup is the
rollback.
Run it from wherever the box's data actually lives: on the host if you're going container-to-host,
or docker compose exec panella-http panella backup --out /app/data/backup.tar.gz inside the
facade container, then copy the archive out.
Step 1 — Bump the pin
Edit whichever of these applies to your deployment:
- Docker image — bump the tag pinned in your
docker-compose.yml/Dockerfile(or the registry tag you pull), then:
bash
docker compose pull # or: docker compose build
docker compose up -d
- Package install — bump the
panellaversion in yourpyproject.toml/requirements.txt, then reinstall (pip install -U panellaor your project's equivalent) and restart thepanella-httpprocess.
Step 2 — Verify
curl -sf http://127.0.0.1:8001/v1/health
/v1/health must return 200. If you've already run panella init, also re-verify the
provisioning:
panella init --verify
If either check fails, do not keep serving on the new version — go to Restore from backup.
10.67.1 → 11.x: majors are a deliberate migration, not a hand bump
The store contract (the SQLite schema, the tag/metadata shape the facade reads) is pinned — see
the OpenAPI-fixture contract test (tests/test_store_probe_adapter_contract.py) that locks the
memories table shape this facade depends on. A major version bump of the underlying store is not
a routine patch: it is a maintainer-driven migration, tracked publicly in this repo's issue tracker
(the issue #3 pattern) with its own compatibility notes and a rehearsed upgrade path.
Self-hosters should not hand-bump a major version pin. Wait for a tagged Panella release that explicitly supports the new store major, follow that release's own upgrade notes, and treat this file's Step 0–2 as the wrapper around it (backup first, bump, verify) — not a substitute for reading what changed.
The audit-invariant release: receipt-gated finalization
This release makes the approval loop two-phase audited: every approval decision on the box's own surfaces (HTTP, MCP, CLI) is appended to the hash chain before it takes effect, and the finalizer refuses to make any governed write durable without a chain-verified receipt. Three operational consequences:
-
Audit op names changed. The approval surface now emits
approval_decision,approval_finalized,approval_list,approval_refused, and (from the one-time migration)approval_decision_backfill. The old route-levelapprovals_list/approvals_approve/approvals_rejectevents are no longer written. If anything downstream queries the audit log by op name (dashboards, SIEM rules, compliance exports), update those queries at upgrade time — an old-name filter silently matches nothing on the new version. Historical rows keep their old names (append-only log). -
First boot runs a one-time receipt backfill and refuses to serve until it completes. Legacy approved-but-unfinalized rows get attested backfill receipts on startup. If such a row was crashed mid-finalize (
finalizer_state='finalizing') and the store is unreachable at boot, the box deliberately stays refusing (503 on memory/approval routes;/v1/healthstays up and names the reason) rather than attest blind — restart once the store is reachable and the backfill completes. Fresh installs and empty queues are unaffected. -
Telegram-transport deployments: adopt the receipt protocol before upgrading. The shipped surfaces (
local_cliHTTP/MCP/CLI) produce receipts automatically. A deployment pinningkind: telegramdrives approvals through its own out-of-repo handler — that handler must now append the pre-decisionapproval_decisionrecord (with the candidate fingerprint) and pass the returned(seq, hash, sha256)receipt intoapprove_authorized_telegram_candidate, or every new telegram approval will stamp without a receipt and the finalizer will refuse it (fail-closed, rows stuckfinalizer_state='failed'). The migration rescues only pre-upgrade rows. Do not upgrade a telegram-wired box until its handler is updated.
Restore from backup (the rollback story)
panella restore --from ./panella-backup-YYYYMMDD.tar.gz --data-dir ./restored-data
restore verifies every file's sha256 against the backup's manifest before placing anything
on disk, refuses to overwrite existing files unless you pass --force, and re-runs the same
store/audit integrity checks after placing the files — so you get a pass/fail line per file, not a
silent "probably fine."
To roll back a bad upgrade:
- Stop the stack:
docker compose down. - Restore the pre-upgrade backup into the data directories the compose volumes back onto (or a fresh directory you then point the volumes at).
- Revert the image tag / package version pin to the pre-upgrade value.
docker compose up -dand re-run Step 2 — Verify.
A restore's target files are the same four roles a backup captures: store_db, token_db,
audit_db, outbox_db (plus governance_overlay when configured) — whichever of these existed
at backup time.