33 KiB
GovOPlaN Release Dependencies
This document owns release package composition, signed package catalogs, license checks, catalog publishing, migration baselines, and the final release checklist.
Operator runtime configuration and module install/uninstall execution live in
DEPLOYMENT_OPERATOR_GUIDE.md.
Backend Packages
Release installs must not depend on sibling checkout paths. Local development
can keep editable installs and file: WebUI links, but release packaging must
resolve modules from tagged git refs or from a package registry.
Local development:
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m pip install -r requirements-dev.txt
Release install from a core checkout plus tagged module repositories:
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m pip install -r requirements-release.txt
.[server] is resolved relative to the current working directory. If you
create the virtualenv elsewhere, still run the install command from the core
checkout:
cd /mnt/DATA/git/govoplan-core
/tmp/govoplan-release-test/bin/python -m pip install -r requirements-release.txt
requirements-release.txt pins the module repositories to the release tag.
Update those refs when cutting a release:
govoplan-access git@git.add-ideas.de:add-ideas/govoplan-access.git v0.1.6
govoplan-admin git@git.add-ideas.de:add-ideas/govoplan-admin.git v0.1.6
govoplan-tenancy git@git.add-ideas.de:add-ideas/govoplan-tenancy.git v0.1.6
govoplan-organizations git@git.add-ideas.de:add-ideas/govoplan-organizations.git v0.1.6
govoplan-identity git@git.add-ideas.de:add-ideas/govoplan-identity.git v0.1.6
govoplan-policy git@git.add-ideas.de:add-ideas/govoplan-policy.git v0.1.6
govoplan-audit git@git.add-ideas.de:add-ideas/govoplan-audit.git v0.1.6
govoplan-files git@git.add-ideas.de:add-ideas/govoplan-files.git v0.1.6
govoplan-mail git@git.add-ideas.de:add-ideas/govoplan-mail.git v0.1.6
govoplan-campaign git@git.add-ideas.de:add-ideas/govoplan-campaign.git v0.1.6
govoplan-calendar git@git.add-ideas.de:add-ideas/govoplan-calendar.git v0.1.6
WebUI Packages
Local development uses webui/package.json, which may point at sibling module
checkouts while active development is happening.
Release WebUI installs should use webui/package.release.json. It points
module dependencies at the same tagged git repositories. After the module tags
referenced there exist, generate the committed release lockfile without
touching the development package files:
cd /mnt/DATA/git/govoplan-core
scripts/generate-release-lock.sh
cd webui
PATH=/home/zemion/.nvm/versions/node/v22.22.3/bin:$PATH /home/zemion/.nvm/versions/node/v22.22.3/bin/npm run build
The module repositories include root-level npm package manifests so git
installs can resolve @govoplan/access-webui, @govoplan/admin-webui,
@govoplan/files-webui, @govoplan/mail-webui,
@govoplan/campaign-webui, and @govoplan/calendar-webui from repository
roots even though their source lives below webui/src.
Release Lockfile Strategy
The supported release composition currently is the full GovOPlaN product: core
plus access, admin, tenancy, organizations, identity, policy, audit,
dashboard, files, mail, campaign, calendar, docs, and ops. Keep one committed
full-product release lockfile at
webui/package-lock.release.json, generated from
webui/package.release.json in a clean release workspace. Development
package-lock.json may continue to point at local file: dependencies.
Frontend module permutations are regression-tested through
GOVOPLAN_WEBUI_MODULE_PACKAGES and temporary build output, not through
committed lockfiles for every possible combination. If a smaller composition
becomes a separately shipped product, add an explicit release manifest and
lockfile pair for that product, for example
package.release.files-mail.json and package-lock.release.files-mail.json,
generated in a clean release workspace from tagged git dependencies.
Release Tag Script
The normal release path is automated by scripts/push-release-tag.sh: it bumps
or accepts the target version, updates Python/WebUI/module manifest versions,
commits/tags/pushes the module repositories first, regenerates
webui/package-lock.release.json, and then commits/tags/pushes core. If the
working tree has already been bumped, pass the current version explicitly:
cd /mnt/DATA/git/govoplan-core
scripts/push-release-tag.sh --version 0.1.6
scripts/generate-release-catalog.py reads installed/discovered
ModuleManifest objects while writing catalog entries. When a manifest is
available, the catalog entry uses the manifest version, points package refs at
v<manifest.version>, and copies provides_interfaces /
requires_interfaces from the manifest. It also copies module migration order
and migration_tasks metadata when present. If a manifest cannot be
discovered, the entry falls back to the release version passed with --version
and omits interface and migration-task metadata. This keeps the catalog aligned
with independently versioned module packages instead of relying on a hardcoded
compatibility table.
The script also includes GovOPlaN roadmap/scaffold module repositories that do
not yet have package metadata. Those repositories are committed, tagged, and
pushed with the same release tag, but they are tag-only until they contain
pyproject.toml, module manifests, or WebUI packages. Tag-only repositories
are not listed in requirements-release.txt or webui/package.release.json.
Current tag-only module repositories:
govoplan-addressesgovoplan-appointmentsgovoplan-casesgovoplan-connectorsgovoplan-dmsgovoplan-erpgovoplan-fit-connectgovoplan-formsgovoplan-identity-trustgovoplan-idmgovoplan-ledgergovoplan-notificationsgovoplan-paymentsgovoplan-portalgovoplan-reportinggovoplan-schedulinggovoplan-searchgovoplan-tasksgovoplan-templatesgovoplan-workflowgovoplan-xoevgovoplan-xrechnunggovoplan-xta-osci
Catalog Trust And Licensing
GovOPlaN module install and uninstall must remain operator-controlled. The running server may plan and validate package changes, but package mutation is performed by the separate installer daemon or an operator shell during maintenance mode.
govoplan-web is the public static distribution surface for official catalog
resources:
- signed module package catalogs, grouped by release channel
- public catalog keyrings
- public license verification keyrings
- examples and operator-facing download paths
govoplan-core is the verifier and orchestrator:
- fetches a local or remote module catalog
- verifies catalog signatures against configured trusted keys
- enforces approved release channels
- rejects expired or not-yet-valid catalogs
- records accepted catalog sequence numbers for replay protection
- checks catalog entry license feature requirements before planning installs
- writes installer plans and request records
Feature and platform modules own their package artifacts, manifests, migration metadata, retirement providers, and optional lifecycle behavior.
Core accepts either a local catalog file or a remote URL:
GOVOPLAN_MODULE_PACKAGE_CATALOG=/srv/govoplan/catalogs/stable.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_URL=https://govoplan.example/catalogs/v1/channels/stable.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_CACHE=/srv/govoplan/runtime/catalog-cache/stable.json
If both file and URL are set, the URL wins. The cache is used when a remote fetch fails, so an operator can still inspect the last known catalog. A cached catalog must still pass signature, freshness, channel, and replay validation.
An official catalog is a JSON object with:
catalog_versionchannelsequencegenerated_atnot_beforewhen delayed activation is neededexpires_atmodulessignatures
Each module entry can declare:
- backend package name and pinned install reference
- WebUI package name and pinned install reference
- display metadata and tags
license_features, the feature entitlements required to plan that installdependenciesandoptional_dependencies, the module ids expected in the target module setmigration_safety, one ofautomatic,requires_review,forward_only, ordestructivemigration_notes, operator-facing data/migration guidance for review, forward-only, or destructive changesmigration_afterandmigration_before, explicit module ids used to order module-owned migration heads when a release needs a live-data sequencing rulemigration_tasks, constrained live-data tasks that run around Alembic migration phases. Each task declarestask_id,phase,summary,task_version,safety,idempotent, and optionallytimeout_seconds. The allowed phases arepre_migration_check,pre_migration_prepare,post_migration_backfill, andpost_migration_verify.current_version_minandcurrent_version_max_exclusive, the installed version window from which this catalog target may be applied directlybridge_releaseandbridge_notes, marking a target as an intermediate compatibility release in a staged update pathallow_downgradeandallow_same_version, explicit opt-ins for reviewed rollback or package-refresh plansrecovery_testedandrecovery_notes, documenting the rehearsal for forward-only or destructive data changesprovides_interfaces, named interface contracts exported by this modulerequires_interfaces, named interface contracts and version ranges required by this module
The signature is Ed25519 over canonical JSON with both signature and
signatures removed. Core accepts the legacy single signature field and the
new signatures array.
When APP_ENV is prod or production, module package catalog signature
verification is required by default unless
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=false is set explicitly.
Independent Module Versions And Interface Ranges
Modules do not need to ship on the same version number. Release catalogs should pin each package to the exact backend/WebUI ref being installed and declare any cross-module API compatibility through named interfaces.
Provider shape:
"provides_interfaces": [
{ "name": "files.campaign_attachments", "version": "1.4.0" }
]
Requirement shape:
"requires_interfaces": [
{
"name": "files.campaign_attachments",
"version_min": "1.0.0",
"version_max_exclusive": "2.0.0"
}
]
version_min is inclusive. version_max_exclusive is exclusive, so the range
above means >= 1.0.0 and < 2.0.0. Use this for SemVer major-version
compatibility lines. Set "optional": true only when the module can operate
without that interface being present. If a provider is installed but its
version is outside the declared optional range, activation is still blocked.
Catalog validation normalizes these fields and warns when catalog entries do not satisfy each other's ranges. Registry activation and installer preflight perform the blocking checks against the discovered installed manifests before the desired module set is activated. The admin module-management UI shows catalog warnings in the package catalog section and repeats them as warning-level installer preflight issues while a package install is planned.
Install-plan items carry a source field. Manually entered items use
source: "manual"; entries planned from the package catalog use
source: "catalog". Catalog-sourced items also carry a catalog metadata
object with the validation snapshot used when the item was planned: catalog
source/path, source type, cache path, channel, sequence, generated/validity
timestamps, signature state, trusted key id, and cache state where available.
Catalog provenance changes preflight severity:
- catalog-sourced installs and updates require a configured, valid package catalog before activation
- invalid, untrusted, expired, not-yet-valid, replayed, or unapproved-channel catalogs block catalog-sourced installs and updates
- the same catalog validation failures remain warnings for manual install plans, so operators can still use offline or emergency package refs
- valid-catalog warnings, such as intentionally unsigned local catalogs when signature enforcement is disabled, remain warnings
- selected catalog entries with unsatisfied non-optional named interface ranges block activation before the installer runs
- selected catalog entries whose target dependencies are neither installed nor planned block activation before the installer runs
- catalog update targets older than the installed module version block unless
the catalog entry declares
allow_downgrade: true - catalog update targets equal to the installed module version block unless the
catalog entry declares
allow_same_version: true - catalog update targets with a
current_version_min/current_version_max_exclusivewindow block when the installed version is outside that window; publish and apply a bridge release instead - catalog entries marked
forward_onlyordestructiveblock activation until the plan row has an explicit data-safety acknowledgement - catalog entries marked
forward_onlyordestructivealso block unless the catalog entry declaresrecovery_tested: trueand either the catalog entry or operator plan row contains recovery notes - catalog entries marked
destructivealso require catalog migration notes or operator notes describing the cleanup or retirement plan
Update Paths
Package updates are target-state operations, not one-module-at-a-time runtime toggles. The safe unit of planning is a desired module version set plus a catalog validation snapshot. The installer may install multiple packages into the environment before activation, then validate the discovered manifests and activate the resulting set together.
Install-plan rows support explicit install, update, and uninstall
actions. Catalog planning writes update when the module is already installed.
Preflight resolves the target set from installed manifests plus the planned
catalog entries. Unplanned catalog entries are not treated as installed. When a
catalog entry would satisfy a missing dependency or named interface, preflight
blocks activation with a companion-update issue; the admin catalog planner adds
those companion rows automatically when it can resolve them from the current
catalog. The preflight response also includes a structured target_plan summary
with each planned module's action, current version, catalog target version,
package refs, migration-safety level, current-version update window, bridge
metadata, recovery metadata, and acknowledgement state.
Database migrations are planned against that same target module set. When the
installer is run with --migrate, it calls govoplan_core.commands.init_db
with the target enabled modules rather than the pre-update startup module list,
so newly installed module migration directories are discovered before
activation. Preflight also returns a structured migration plan. Its step order is
derived from:
- manifest and catalog
migration_after/migration_beforedeclarations - module dependencies and optional dependencies when both modules are in the target plan
- named interface provider/consumer relationships when both sides are in the target plan
Preflight blocks cycles in that ordering graph. It also blocks non-idempotent
module migration tasks, forward-only/destructive tasks without operator
acknowledgement, and installed manifest tasks that declare no executor.
Catalog-only task executors are marked as pending because they can only be
confirmed after the target package is installed. The migrator runs pre-migration
tasks, upgrades the ordered module heads first, finishes with Alembic heads,
and then runs post-migration tasks, so Alembic's revision graph remains
authoritative while GovOPlaN still gives operators a module-aware live-data
order.
This avoids circular "upgrade A first / upgrade B first" traps: named interface requirements are solved against the target set, not against each intermediate package-install moment. If the target set cannot satisfy all non-optional interfaces and module dependencies at once, the plan is invalid. Operators should add the necessary module updates to the same plan instead of trying to force an order.
Live data upgrades need an even stricter rule:
- migrations must be idempotent and ordered by module migration metadata
- destructive schema/data changes need an explicit retirement or cleanup plan, not an automatic package update side effect
- cross-module data migrations must be compatible with both the old and target provider interface until activation finishes
- rollback must restore the package set and database state together, or be documented as forward-only with a tested recovery procedure
- if two modules require mutually incompatible live-data states, the catalog must publish an intermediate compatibility release rather than a circular update chain
The release catalog is the first safety gate for this. Generated catalogs mark
modules with registered migrations as requires_review by default. Release
authors should keep that value for ordinary reversible migrations, raise it to
forward_only when database rollback requires restoring a snapshot, and raise
it to destructive when the update removes or irreversibly rewrites persisted
data. Forward-only and destructive entries must include recovery_tested: true
and recovery notes after a verified restore or forward-recovery rehearsal. The
admin install-plan UI exposes the safety level and lets operators record an
explicit acknowledgement; preflight keeps acknowledged forward-only/destructive
changes visible as warnings.
In practice, circular dependencies are avoided by designing interfaces with
compatibility windows and by publishing bridge releases. A bridge release keeps
the old interface while introducing the new one, allowing dependent modules to
move first; a later release can retire the old interface after every dependent
module has a compatible target version. Use current_version_min and
current_version_max_exclusive to make those direct-update windows explicit in
the catalog, and set bridge_release: true on intermediate targets that exist
primarily to carry installations safely across a compatibility gap.
Trusted catalog keys are configured locally:
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE=/srv/govoplan/trust/catalog-keyring.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS='{"release-key-1":"<base64 public key>"}'
For development or tightly controlled deployments, a keyring can be read from a URL and cached:
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_URL=https://govoplan.example/catalogs/v1/keyring.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_CACHE=/srv/govoplan/runtime/catalog-cache/keyring.json
Production installations should pin the trusted keyring locally or ship it through deployment configuration. Fetching trusted keys from the same public origin as the catalog is convenient, but that origin must not become the only trust root.
Dependency Audits
Dependency vulnerability checks are documented in
DEPENDENCY_AUDITS.md. The local audit runner is:
cd /mnt/DATA/git/govoplan-core
bash scripts/check-dependency-audits.sh
The Gitea workflow in .gitea/workflows/dependency-audit.yml runs the same
check against release dependency refs on pushes, pull requests, and a weekly
schedule.
Keyring entries support:
key_idpublic_keyorpublic_key_base64status:active,next,retired,revoked, ordisablednot_beforenot_after
Rotation process:
- Add the next public key to the local trusted keyring with status
next. - Publish catalogs signed by both current and next keys.
- Upgrade installations so the next key is locally trusted.
- Promote the next key to
active. - Retire the old key only after every supported installation trusts the new key.
- Mark a compromised key
revokedand publish a higher sequence catalog signed by an uncompromised key.
Use replay state in production:
GOVOPLAN_MODULE_PACKAGE_CATALOG_SEQUENCE_STATE=/srv/govoplan/runtime/catalog-sequences.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE=true
Core records the accepted sequence per channel after a catalog entry is planned from the admin interface. With strict sequence enforcement, a previously accepted sequence is rejected; without strict enforcement, only older sequences are rejected. Catalogs should always expire.
The sequence state file is operational state, not a trust root. Keep it on persistent storage and include it in normal backups:
{
"channels": {
"stable": {
"last_sequence": 42,
"accepted_at": "2026-07-07T12:00:00Z",
"key_id": "release-key-1",
"source": "https://govoplan.example/catalogs/v1/channels/stable.json"
}
}
}
If the file is lost, restore it from backup. If no backup exists, reconstruct
each channel from the highest sequence already accepted in installer run
records, release records, or the currently deployed module package set. Do not
lower last_sequence to make an older catalog pass; publish a new higher
sequence catalog when the accepted point is uncertain.
If the file is corrupted, copy it aside for incident review, validate the
current signed catalog with channel and freshness enforcement, then rewrite the
state with the known accepted sequence. Keep
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true and approved-channel
checks enabled during recovery. Temporarily disabling
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE allows revalidating the same
sequence, but older sequences remain rejected once the reconstructed
last_sequence is in place.
Approved channels are deployment policy:
GOVOPLAN_MODULE_PACKAGE_CATALOG_APPROVED_CHANNELS=stable,lts
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true
The admin UI can display other catalog metadata, but core rejects catalogs from unapproved channels when validation is configured.
Catalog entries can require license features:
"license_features": ["module.mail", "support.standard"]
Core checks those requirements against an offline license file before allowing the entry into the install plan.
GOVOPLAN_LICENSE_FILE=/srv/govoplan/license.json
GOVOPLAN_LICENSE_ENFORCEMENT=true
GOVOPLAN_LICENSE_TRUSTED_KEYS_FILE=/srv/govoplan/trust/license-keyring.json
License files are JSON objects with:
license_idsubjectfeaturesvalid_fromvalid_untilsignature
Issue or renew a license from an operator/release shell that has the Ed25519 private key:
govoplan-module-installer \
--issue-license /srv/govoplan/license.json \
--license-id customer-2026-07 \
--license-subject "Example Municipality" \
--license-feature module.mail \
--license-feature support.standard \
--license-valid-until 2027-07-31T23:59:59Z \
--license-signing-key-id license-issuer-1 \
--license-signing-private-key /srv/govoplan/secrets/license-issuer-1.pem \
--format json
Validate an imported license without exposing secrets:
govoplan-module-installer \
--validate-license /srv/govoplan/license.json \
--license-trusted-key license-issuer-1="<base64 public key>" \
--require-trusted-license \
--license-required-feature module.mail \
--format json
The CLI and admin module catalog panel report the license id, subject, validity window, signing key id, signed/trusted state, available features, and missing entitlements for the configured package catalog. They do not expose private signing material.
License enforcement can run in observe-only mode by leaving
GOVOPLAN_LICENSE_ENFORCEMENT unset. In that mode, missing or invalid license
data is surfaced as a warning but does not block planning.
Renewal is an ordinary re-issuance with a new license_id, extended
valid_until, and the full intended feature set. Import the renewed JSON to
GOVOPLAN_LICENSE_FILE, keep the previous file for audit, and validate it
before setting enforcement.
Revocation is handled through the trusted license keyring. Mark a compromised
or invalid issuer key as revoked or disabled, publish or deploy the updated
keyring, then reissue affected licenses with an active key. Installations that
run with GOVOPLAN_LICENSE_ENFORCEMENT=true reject licenses signed only by a
revoked key after the local keyring is updated.
Emergency fallback is deliberately explicit. Operators can temporarily unset
GOVOPLAN_LICENSE_ENFORCEMENT to keep package planning observable while a
license or keyring is recovered. Record the change in the operational incident
log, keep catalog signature and channel enforcement enabled, and restore
license enforcement after a trusted renewal validates successfully.
Licensing is intentionally separate from open-source code licensing. The catalog/license mechanism can govern support channels, official release eligibility, hosted update access, professional support, or commercial entitlements without changing the source license of the repositories.
Production-grade distribution still needs remote registry/git artifact
resolution before package-manager apply, a hardened catalog publishing pipeline
in govoplan-web, and automated key rotation and emergency revocation drills.
Release Catalog Publishing
GovOPlaN release catalogs are published by govoplan-web as static JSON and
verified by govoplan-core before installer plans are accepted. Private signing
keys must stay outside all git repositories. Public keyrings are published with
the website.
Create the first catalog signing key on the release machine:
cd /mnt/DATA/git/govoplan-core
KEY_DIR="$HOME/.config/govoplan/release-keys"
mkdir -p "$KEY_DIR"
./.venv/bin/python scripts/generate-catalog-keypair.py \
--key-id release-key-1 \
--private-key "$KEY_DIR/release-key-1.pem" \
--public-key "$KEY_DIR/release-key-1.pub" \
--keyring "$KEY_DIR/catalog-keyring.json"
Keep release-key-1.pem private. The generated keyring contains only public
material.
Generate the signed catalog into govoplan-web:
cd /mnt/DATA/git/govoplan-core
KEY_DIR="$HOME/.config/govoplan/release-keys"
scripts/publish-release-catalog.sh \
--version <x.y.z> \
--sequence 202607071340 \
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
--build-web
This writes:
/mnt/DATA/git/govoplan-web/public/catalogs/v1/channels/stable.json/mnt/DATA/git/govoplan-web/public/catalogs/v1/keyring.json
The wrapper validates the catalog with core using the generated public keyring.
For normal module/core releases, first audit and record migration baselines, then tag and push the module/core repos. Finally publish the website catalog:
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python scripts/release-migration-audit.py --strict
cd /mnt/DATA/git/govoplan-core
KEY_DIR="$HOME/.config/govoplan/release-keys"
scripts/publish-release-catalog.sh \
--version <x.y.z> \
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
--build-web \
--commit \
--tag \
--push
The website tag is catalog-v<x.y.z>. The public URL is:
https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json
The public keyring URL is:
https://govoplan.add-ideas.de/catalogs/v1/keyring.json
scripts/push-release-tag.sh can publish the web catalog after module and core
tags have been pushed. It runs the migration release audit in automatic mode:
warning-only before the first recorded migration baseline, strict after a
baseline exists. Add --strict-migration-audit when you want to force strict
mode explicitly:
cd /mnt/DATA/git/govoplan-core
KEY_DIR="$HOME/.config/govoplan/release-keys"
scripts/push-release-tag.sh \
--bump subversion \
--strict-migration-audit \
--publish-web-catalog \
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \
--build-web-catalog
Use --catalog-signing-key more than once during a key rotation window. The
catalog will contain multiple signatures and the public keyring will include the
corresponding public keys.
On a GovOPlaN installation that should consume the official stable catalog:
GOVOPLAN_MODULE_PACKAGE_CATALOG_URL=https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_CACHE=/srv/govoplan/runtime/catalog-cache/stable.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_REQUIRE_SIGNATURE=true
GOVOPLAN_MODULE_PACKAGE_CATALOG_APPROVED_CHANNELS=stable
GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE=/srv/govoplan/trust/catalog-keyring.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_SEQUENCE_STATE=/srv/govoplan/runtime/catalog-sequences.json
GOVOPLAN_MODULE_PACKAGE_CATALOG_ENFORCE_SEQUENCE=true
For production, copy the public keyring into deployment configuration and pin it locally. Do not rely on a URL-fetched keyring as the only trust root.
stable.json includes a top-level core_release section for operator/update
tooling. Core is intentionally not listed as a normal module entry because it
must not be added to saved enabled-module state. Core upgrades should remain an
operator-supervised package update with restart and health checks.
Key rotation for published catalogs:
- Generate the next private key outside git.
- Run
publish-release-catalog.shwith both signing keys. - Publish the web catalog/keyring.
- Roll the new public keyring into installations.
- Stop signing with the old key after the supported fleet trusts the new key.
- Mark compromised keys as revoked in the public keyring and publish a higher sequence catalog signed by a trusted uncompromised key.
PostgreSQL Release Check
Release candidates should pass a disposable PostgreSQL migration and startup smoke check before tagging or publishing catalogs. Start the local testbed, then run the permutation check from the core checkout:
cd /mnt/DATA/git/govoplan-core/dev/postgres
cp .env.example .env
docker compose --env-file .env up -d
cd /mnt/DATA/git/govoplan-core
set -a
. dev/postgres/.env
set +a
./.venv/bin/python scripts/postgres-integration-check.py \
--database-url "$GOVOPLAN_POSTGRES_DATABASE_URL" \
--reset-schema
The script checks migrations and /health startup for core-only, files-only,
mail-only, campaign-only, campaign+files, campaign+mail, and full-product
module sets. --reset-schema is destructive and must only be used against a
throwaway database.
Migration Baselines
Development migrations may be small and numerous while a feature is moving. Before a stable release, unreleased migrations may be rewritten or squashed into a release-level baseline or release-to-release upgrade migration. After a release tag has shipped, released migration revision IDs are immutable.
The release policy is:
- unreleased migrations may be folded before release;
- released migrations are never rewritten or deleted;
- each stable release records the public migration head revisions in
docs/migration-release-baselines.json; - fresh installations should apply release-level baselines/upgrades, not unreleased create-then-rename churn;
- release-to-release schema changes should be folded into one reviewed migration per migration owner where practical.
Audit the current graph during release preparation:
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python scripts/release-migration-audit.py
Generate the reviewed/manual squash checklist:
./.venv/bin/python scripts/release-migration-audit.py --squash-plan
After the release migrations have been reviewed and the graph is final, record the release baseline:
./.venv/bin/python scripts/release-migration-audit.py --record-release <x.y.z>
Use strict mode to verify that the current heads are recorded:
./.venv/bin/python scripts/release-migration-audit.py --strict
scripts/push-release-tag.sh runs the audit by default in automatic mode:
non-strict while no release baseline exists, strict after the first baseline is
recorded. Pass --warn-migration-audit for an explicit non-strict audit,
--strict-migration-audit to force strict mode, or --skip-migration-audit
only for emergency/manual release work.
Before the first stable release, fold the current development chain into the
first public baseline and record that baseline in
docs/migration-release-baselines.json. The tracking issue is
add-ideas/govoplan-core#223.
Related Operator Documents
DEPLOYMENT_OPERATOR_GUIDE.md: runtime environment, explicit migrations, backup/restore commands, module installer daemon/supervisor operation, and rollback drills.REMOTE_WEBUI_BUNDLES.md: experimental browser-loaded module bundles for controlled deployments; normal releases use package builds.
Release Checklist
- Keep Python package versions, WebUI package versions, and git tags aligned.
- Tag core, access, admin, tenancy, policy, audit, files, mail, campaign, calendar, and scaffold module repositories together.
- Update
requirements-release.txtandwebui/package.release.jsonwhen the release tag changes. - Generate the committed full-product release lockfile from
package.release.jsonwithscripts/generate-release-lock.sh. - Run
scripts/release-migration-audit.py --strictafter recording a release baseline. - Run the PostgreSQL release check against a disposable database.
- Publish the signed catalog through the release catalog publishing flow above.
- Add separate release manifest/lockfile pairs only for module compositions that are shipped as their own products.
- Do not commit local sibling paths into release manifests.