From 95005cddbce585e3ae6855f4820062d35b512e40 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Thu, 6 Aug 2026 22:52:49 +0200 Subject: [PATCH] Sync Repo-docs-RELEASE-DEPENDENCIES from project files --- Repo-docs-RELEASE-DEPENDENCIES.-.md | 966 ++++++++++++++++++++++++++++ 1 file changed, 966 insertions(+) create mode 100644 Repo-docs-RELEASE-DEPENDENCIES.-.md diff --git a/Repo-docs-RELEASE-DEPENDENCIES.-.md b/Repo-docs-RELEASE-DEPENDENCIES.-.md new file mode 100644 index 0000000..d9b57cc --- /dev/null +++ b/Repo-docs-RELEASE-DEPENDENCIES.-.md @@ -0,0 +1,966 @@ + + +> Mirrored from `/mnt/DATA/git/govoplan-core/docs/RELEASE_DEPENDENCIES.md`. +> Origin: `repository`. +> Active tasks and changing state belong in Gitea issues; this wiki page is durable project context. + +--- +# 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: + +```bash +cd /mnt/DATA/git/govoplan +./.venv/bin/python -m pip install -r requirements-dev.txt +``` + +Release install from the meta checkout plus tagged module repositories: + +```bash +cd /mnt/DATA/git/govoplan +./.venv/bin/python -m pip install -r requirements-release.txt +``` + +`../govoplan-core[server]` is resolved relative to the meta requirements file. +If you create the virtualenv elsewhere, still run the install command from the +meta checkout: + +```bash +cd /mnt/DATA/git/govoplan +/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: + +```text +govoplan-tenancy git@git.add-ideas.de:GovOPlaN/govoplan-tenancy.git v0.1.8 +govoplan-organizations git@git.add-ideas.de:GovOPlaN/govoplan-organizations.git v0.1.8 +govoplan-identity git@git.add-ideas.de:GovOPlaN/govoplan-identity.git v0.1.8 +govoplan-idm git@git.add-ideas.de:GovOPlaN/govoplan-idm.git v0.1.8 +govoplan-access git@git.add-ideas.de:GovOPlaN/govoplan-access.git v0.1.8 +govoplan-admin git@git.add-ideas.de:GovOPlaN/govoplan-admin.git v0.1.8 +govoplan-policy git@git.add-ideas.de:GovOPlaN/govoplan-policy.git v0.1.8 +govoplan-audit git@git.add-ideas.de:GovOPlaN/govoplan-audit.git v0.1.8 +govoplan-dashboard git@git.add-ideas.de:GovOPlaN/govoplan-dashboard.git v0.1.8 +govoplan-addresses git@git.add-ideas.de:GovOPlaN/govoplan-addresses.git v0.1.8 +govoplan-files git@git.add-ideas.de:GovOPlaN/govoplan-files.git v0.1.8 +govoplan-mail git@git.add-ideas.de:GovOPlaN/govoplan-mail.git v0.1.8 +govoplan-campaign git@git.add-ideas.de:GovOPlaN/govoplan-campaign.git v0.1.8 +govoplan-calendar git@git.add-ideas.de:GovOPlaN/govoplan-calendar.git v0.1.8 +govoplan-poll git@git.add-ideas.de:GovOPlaN/govoplan-poll.git v0.1.8 +govoplan-scheduling git@git.add-ideas.de:GovOPlaN/govoplan-scheduling.git v0.1.8 +govoplan-notifications git@git.add-ideas.de:GovOPlaN/govoplan-notifications.git v0.1.8 +govoplan-evaluation git@git.add-ideas.de:GovOPlaN/govoplan-evaluation.git v0.1.8 +govoplan-docs git@git.add-ideas.de:GovOPlaN/govoplan-docs.git v0.1.8 +govoplan-ops git@git.add-ideas.de:GovOPlaN/govoplan-ops.git v0.1.8 +``` + +## 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: + +```bash +cd /mnt/DATA/git/govoplan +tools/release/generate-release-lock.sh +cd /mnt/DATA/git/govoplan-core/webui +PATH=/home/zemion/.nvm/versions/node/v22.22.3/bin:$PATH /home/zemion/.nvm/versions/node/v22.22.3/bin/npm run build +``` + +Module repositories with a frontend include root-level npm package manifests +so the `@govoplan/*-webui` dependencies in `webui/package.release.json` can be +resolved from repository roots even though their source lives below +`webui/src`. + +### Release Lockfile Strategy + +The supported backend release composition is the set pinned in the meta +repository's `requirements-release.txt`. The supported frontend composition +is the independently buildable module set pinned in +`webui/package.release.json`; backend-only modules do not need a frontend +package entry. 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 `/mnt/DATA/git/govoplan/tools/release/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: + +```bash +cd /mnt/DATA/git/govoplan +tools/release/push-release-tag.sh --version 0.1.8 +``` + +`/mnt/DATA/git/govoplan/tools/release/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`, 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-appointments` +- `govoplan-cases` +- `govoplan-connectors` +- `govoplan-dms` +- `govoplan-erp` +- `govoplan-fit-connect` +- `govoplan-forms` +- `govoplan-identity-trust` +- `govoplan-ledger` +- `govoplan-payments` +- `govoplan-portal` +- `govoplan-postbox` +- `govoplan-reporting` +- `govoplan-search` +- `govoplan-tasks` +- `govoplan-templates` +- `govoplan-workflow-engine` (headless definitions, migrations, and execution) +- `govoplan-workflow` (optional authoring and inspection WebUI) +- `govoplan-xoev` +- `govoplan-xrechnung` +- `govoplan-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. + +`addideas-govoplan-website` 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: + +```bash +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. + +If neither source is configured, the Admin package directory discovers the +official public stable catalog at +`https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json`. Core verifies +that fallback against the public key pinned in the installed Core package. An +explicit deployment catalog always takes precedence; a configured source that +is unavailable or invalid fails closed instead of silently falling back. + +An official catalog is a JSON object with: + +- `catalog_version` +- `channel` +- `sequence` +- `generated_at` +- `not_before` when delayed activation is needed +- `expires_at` +- `modules` +- `signatures` + +Each module entry can declare: + +- backend package name and pinned install reference +- WebUI package name and pinned install reference +- `artifact_integrity` for each package, including the HTTPS registry URL, + filename, byte size, SHA-256, package identity, source tag, and source commit +- `source`, binding the repository and immutable tag/commit identity, with + optional HTTPS repository and revision links +- `availability`, either `available` or `withdrawn`; a withdrawn entry must + carry an operator-readable `availability_reason` and cannot be planned +- `configuration_requirements` and an optional HTTPS `release_notes_url` for + prerequisites and release-specific operator guidance +- display metadata and tags +- `license_features`, the feature entitlements required to plan that install +- `dependencies` and `optional_dependencies`, the module ids expected in the + target module set +- `migration_safety`, one of `automatic`, `requires_review`, `forward_only`, + or `destructive` +- `migration_notes`, operator-facing data/migration guidance for review, + forward-only, or destructive changes +- `migration_after` and `migration_before`, explicit module ids used to order + module-owned migration heads when a release needs a live-data sequencing rule +- `migration_tasks`, constrained live-data tasks that run around Alembic + migration phases. Each task declares `task_id`, `phase`, `summary`, + `task_version`, `safety`, `idempotent`, and optionally `timeout_seconds`. + The allowed phases are `pre_migration_check`, `pre_migration_prepare`, + `post_migration_backfill`, and `post_migration_verify`. +- `current_version_min` and `current_version_max_exclusive`, the installed + version window from which this catalog target may be applied directly +- `bridge_release` and `bridge_notes`, marking a target as an intermediate + compatibility release in a staged update path +- `allow_downgrade` and `allow_same_version`, explicit opt-ins for reviewed + rollback or package-refresh plans +- `recovery_tested` and `recovery_notes`, documenting the rehearsal for + forward-only or destructive data changes +- `provides_interfaces`, named interface contracts exported by this module +- `requires_interfaces`, named interface contracts and version ranges required + by this module + +Core validates these fields before exposing the directory. Admin derives a +read-only catalog state from the installed package set, catalog dependency +closure, named-interface providers, current-version window, availability, and +generic license policy. This is an early operator diagnostic; trusted installer +preflight remains the authoritative mutation gate. + +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: + +```json +"provides_interfaces": [ + { "name": "files.campaign_attachments", "version": "1.4.0" } +] +``` + +Requirement shape: + +```json +"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 +- a saved catalog plan must match the currently validated entry exactly; + altered package refs, artifact identities, channel, sequence, trust state, or + signing-key identity block the run and require replanning +- a trusted remote artifact is downloaded before mutation into a private + SHA-256-addressed installer cache, checked for exact size and digest, and + passed to `pip` or npm only as that verified local file +- 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_exclusive` window block when the installed version is + outside that window; publish and apply a bridge release instead +- catalog entries marked `forward_only` or `destructive` block activation until + the plan row has an explicit data-safety acknowledgement +- catalog entries marked `forward_only` or `destructive` also block unless the + catalog entry declares `recovery_tested: true` and either the catalog entry or + operator plan row contains recovery notes +- catalog entries marked `destructive` also 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_before` declarations +- 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: + +```bash +GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS_FILE=/srv/govoplan/trust/catalog-keyring.json +GOVOPLAN_MODULE_PACKAGE_CATALOG_TRUSTED_KEYS='{"release-key-1":""}' +``` + +For development or tightly controlled deployments, a keyring can be read from a +URL and cached: + +```bash +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`](DEPENDENCY_AUDITS.md). The local audit runner is: + +```bash +cd /mnt/DATA/git/govoplan +bash tools/checks/check-dependency-audits.sh +``` + +The Gitea workflow in `govoplan/.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_id` +- `public_key` or `public_key_base64` +- `status`: `active`, `next`, `retired`, `revoked`, or `disabled` +- `not_before` +- `not_after` + +Rotation process: + +1. Add the next public key to the local trusted keyring with status `next`. +2. Publish catalogs signed by both current and next keys. +3. Upgrade installations so the next key is locally trusted. +4. Promote the next key to `active`. +5. Retire the old key only after every supported installation trusts the new + key. +6. Mark a compromised key `revoked` and publish a higher sequence catalog + signed by an uncompromised key. + +Use replay state in production: + +```bash +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: + +```json +{ + "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: + +```bash +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: + +```json +"license_features": ["module.mail", "support.standard"] +``` + +Core checks those requirements against an offline license file before allowing +the entry into the install plan. + +Official open-source GovOPlaN entries do not declare license features. The +license contract remains generic for external catalogs, deployment presets, +configuration/package directories, and support offerings; it gates only an +entry that explicitly asks for a feature. + +```bash +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_id` +- `subject` +- `features` +- `valid_from` +- `valid_until` +- `signature` + +Issue or renew a license from an operator/release shell that has the Ed25519 +private key: + +```bash +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: + +```bash +govoplan-module-installer \ + --validate-license /srv/govoplan/license.json \ + --license-trusted-key license-issuer-1="" \ + --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 +that writes to `addideas-govoplan-website`, and automated key rotation and +emergency revocation drills. + +## Release Catalog Publishing + +GovOPlaN release catalogs are published to `addideas-govoplan-website` 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: + +```bash +cd /mnt/DATA/git/govoplan +KEY_DIR="$HOME/.config/govoplan/release-keys" +mkdir -p "$KEY_DIR" +./.venv/bin/python tools/release/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 `addideas-govoplan-website`: + +```bash +cd /mnt/DATA/git/govoplan +KEY_DIR="$HOME/.config/govoplan/release-keys" +tools/release/publish-release-catalog.sh \ + --version \ + --sequence 202607071340 \ + --catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \ + --build-web +``` + +This writes: + +- `/mnt/DATA/git/addideas-govoplan-website/public/catalogs/v1/channels/stable.json` +- `/mnt/DATA/git/addideas-govoplan-website/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: + +```bash +cd /mnt/DATA/git/govoplan +./.venv/bin/python tools/release/release-migration-audit.py --strict +``` + +```bash +cd /mnt/DATA/git/govoplan +KEY_DIR="$HOME/.config/govoplan/release-keys" +tools/release/publish-release-catalog.sh \ + --version \ + --catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" \ + --build-web \ + --commit \ + --tag \ + --push +``` + +The website tag is `catalog-v`. The public URL is: + +```text +https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json +``` + +The public keyring URL is: + +```text +https://govoplan.add-ideas.de/catalogs/v1/keyring.json +``` + +`/mnt/DATA/git/govoplan/tools/release/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: + +```bash +cd /mnt/DATA/git/govoplan +KEY_DIR="$HOME/.config/govoplan/release-keys" +tools/release/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. + +## Release Doctor + +Use `/mnt/DATA/git/govoplan/tools/release/release-doctor.py` before release preparation and again before +publishing. It inspects repository state, local versions, release tags, +migration audit state, release package refs, stable catalog/keyring state, and +optionally public catalog availability. The default mode is read-only and +offline. Status output is intentionally concise: + +```bash +cd /mnt/DATA/git/govoplan +./.venv/bin/python tools/release/release-doctor.py status --target-version +``` + +Use `--details` when the full findings should be printed directly: + +```bash +./.venv/bin/python tools/release/release-doctor.py status --target-version --details +``` + +For concise guidance, print only suggested next commands: + +```bash +./.venv/bin/python tools/release/release-doctor.py next --target-version +``` + +For an operator-guided session, run interactive mode. The doctor asks which +suggested command to run; mutating commands require typing `RUN` before they +execute: + +```bash +./.venv/bin/python tools/release/release-doctor.py interactive --target-version +``` + +Interactive mode starts with a compact summary and waits for an action. It can +open the full report in the configured pager, run one suggested command, repair +Git `safe.directory` dubious-ownership blocks after explicit confirmation, push +all clean repositories that are ahead of their upstream, or commit and push all +dirty repositories. The dirty-repository bulk action asks for a commit message, +skips repositories that are behind upstream, and requires typing +`COMMIT AND PUSH` before it stages, commits, and pushes. + +Add `--online` when remote tag and public catalog/keyring reachability should be +checked. Add `--json` when a CI job or another tool should consume the report. + +On a GovOPlaN installation that should consume the official stable catalog: + +```bash +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: + +1. Generate the next private key outside git. +2. Run `/mnt/DATA/git/govoplan/tools/release/publish-release-catalog.sh` with both signing keys. +3. Publish the web catalog/keyring. +4. Roll the new public keyring into installations. +5. Stop signing with the old key after the supported fleet trusts the new key. +6. 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: + +```bash +cd /mnt/DATA/git/govoplan/dev/postgres +cp .env.example .env +docker compose --env-file .env up -d + +cd /mnt/DATA/git/govoplan +set -a +. /mnt/DATA/git/govoplan/dev/postgres/.env +set +a +tools/checks/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. Before those permutations, the required Core proof runs in +random, test-owned schemas without modifying `public`. It exercises Files' real +credential-owning retirement provider and proves that credential scrubbing, +non-secret audit +insertion, and table retirement commit together; database-injected audit and +DDL failures roll the entire unit back. A 500 ms PostgreSQL `lock_timeout` and +captured backend process IDs also prove that each `DROP TABLE` uses the +installer Session connection instead of waiting through a second connection. +The meta check enables the release-gate flag so missing PostgreSQL configuration +or full-stack test packages are a hard failure; ordinary Core-only test discovery +skips this integration proof. Do not pass `--skip-retirement-atomicity` when +collecting release evidence. + +## Migration Baselines + +Release migration support follows `COMPATIBILITY_POLICY.md`: every tagged +`0.1.x` installation is a supported upgrade origin, released revision IDs are +immutable, and migration-only reconciliation remains available for at least one +subsequent major release cycle after the matching runtime compatibility path is +removed. + +Development migrations may be small and numerous while a feature is moving. +GovOPlaN keeps those detailed migrations on an explicit development track and +publishes reviewed release shortcuts on the release track. Before a stable +release, unreleased development migrations may be 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 development migrations live in `dev_versions` and are not deleted + when a release shortcut is added; +- released migrations live in `versions` and are never rewritten or deleted; +- future release shortcuts are additive. A new release-to-release migration + starts from the previous recorded release heads, so installations can upgrade + through sane release steps instead of replaying every development revision; +- each stable release records the public migration head revisions in + `docs/migration-release-baselines.json`; +- `heads` records the Alembic dependency-leaf graph heads for a full graph, + while `owner_heads` records the latest migration revision per migration + owner for subset installs and review; +- 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. + +The default runtime track is `release`: + +```bash +cd /mnt/DATA/git/govoplan +GOVOPLAN_MIGRATION_TRACK=release ./.venv/bin/python -m govoplan_core.commands.init_db +``` + +Use `dev` only for local/disposable development databases that intentionally +need the detailed chain: + +```bash +cd /mnt/DATA/git/govoplan +GOVOPLAN_MIGRATION_TRACK=dev ./.venv/bin/python -m govoplan_core.commands.init_db +``` + +Production, release checks, operator install flows, and the normal development +launcher should stay on the `release` track unless a fresh/disposable database +is intentionally being used to test the detailed development chain. + +Audit the current graph during release preparation: + +```bash +cd /mnt/DATA/git/govoplan +./.venv/bin/python tools/release/release-migration-audit.py +``` + +Audit the detailed development graph separately when a squash is prepared: + +```bash +./.venv/bin/python tools/release/release-migration-audit.py --track dev +``` + +Generate the reviewed/manual squash checklist: + +```bash +./.venv/bin/python tools/release/release-migration-audit.py --squash-plan +``` + +After the release migrations have been reviewed and the graph is final, record +the release baseline: + +```bash +./.venv/bin/python tools/release/release-migration-audit.py --record-release +``` + +Use strict mode to verify that the current heads are recorded: + +```bash +./.venv/bin/python tools/release/release-migration-audit.py --strict +``` + +`/mnt/DATA/git/govoplan/tools/release/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. + +The first public baseline is v0.1.7. It intentionally adds release-track +shortcuts for the unreleased v0.0.0 -> v0.1.7 development chains while keeping +the detailed chains on the `dev` track. No production installations existed +before that baseline, so pre-v0.1.7 development revisions are not release +upgrade targets. Future release-to-release changes must start from a recorded +release baseline and add a new release-track step-up instead of replacing prior +release shortcuts. The tracking issue is +`GovOPlaN/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 meta `requirements-release.txt` and core `webui/package.release.json` when the + release tag changes. +- Generate the committed full-product release lockfile from + `package.release.json` with `/mnt/DATA/git/govoplan/tools/release/generate-release-lock.sh`. +- Run `/mnt/DATA/git/govoplan/tools/release/release-migration-audit.py --strict` after 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.