Upgrading Panella

August 20, 2026 · View on GitHub

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:

    docker compose pull   # or: docker compose build
    docker compose up -d
    
  • Package install — bump the panella version in your pyproject.toml / requirements.txt, then reinstall (pip install -U panella or your project's equivalent) and restart the panella-http process.

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.

Resolver c2 contract upgrade

CalibrationManifest, VersionStamp, and BlockingReceipt now carry required resolver binding fields. Positional constructors that predate those fields will fail at their construction sites; update callers to pass the complete current contract. Recreate any resolver gate ticket with the new config_hash before it is consumed. Older manifests fail closed when blocking_rules_hash or a required schema key is absent, so generate and verify a fresh manifest rather than editing an old one in place.

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-level approvals_list / approvals_approve / approvals_reject events 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/health stays 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_cli HTTP/MCP/CLI) produce receipts automatically. A deployment pinning kind: telegram drives approvals through its own out-of-repo handler — that handler must now append the pre-decision approval_decision record (with the candidate fingerprint) and pass the returned (seq, hash, sha256) receipt into approve_authorized_telegram_candidate, or every new telegram approval will stamp without a receipt and the finalizer will refuse it (fail-closed, rows stuck finalizer_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:

  1. Stop the stack: docker compose down.
  2. Restore the pre-upgrade backup into the data directories the compose volumes back onto (or a fresh directory you then point the volumes at).
  3. Revert the image tag / package version pin to the pre-upgrade value.
  4. docker compose up -d and 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.