# GovOPlaN Release Console The release console is a local operator tool for planning and executing GovOPlaN releases. It belongs to the `govoplan` meta repository because it works across all local checkouts, release scripts, module manifests, migration audits, catalog files, Git state, and signing keys. The current implementation has a read-only dashboard plus guarded local candidate/publish actions: - inspect repositories from `repositories.json` - show dirty, ahead, behind, missing, no-HEAD, and tag state - show local package catalog and keyring state - optionally run release/dev migration audits - propose next actions without executing them - compare local catalog/keyring JSON with the published channel and public keyring when online checks are enabled - configure target versions per release unit in the web UI - select the repositories that should advance through checkboxes - generate dry-run selective release plans for independently versioned packages - freeze a selective plan as a durable, resumable local release run - create annotated source release tags for selected clean, version-aligned repositories and publish each branch/tag pair atomically - generate signed catalog candidates for the selected release units - preview/apply/push reviewed catalog candidates behind explicit confirmations Start it from the meta repository: ```sh ./.venv/bin/python tools/release/release-console.py ``` The server binds to `127.0.0.1` by default and prints a URL containing a local API token. Open that URL in a browser on the same machine. The web UI starts with a repository table. Each repository can be checked independently and assigned its own target version. Repositories without version metadata remain visible so they can be planned as initial releases. `Build Plan` shows the dry-run commands for the selected rows, and `Generate Candidate` creates a signed catalog candidate that advances only selected repositories that already have a catalog entry. `Build Plan` also returns structured release-gate findings for each selected repository. The plan names the recommended next action and gives an explicit remediation for source-version, lockfile, Core WebUI composition, Git state, and worktree findings. A target version that has not yet been applied consistently to the selected source tree is therefore explained before the source-tag preflight rather than appearing only as an error after the operator tries to tag. `source_preflight_ready` means that the plan-visible source gates pass; the non-mutating `Preview Tag + Publish` remains mandatory for remote, manifest, and immutable-tag checks. ## Durable release runs The **Durable Run State** card turns the current repository/version selection into a versioned local run record. The server rebuilds the selective plan and requires the plan to resolve exactly the requested repositories and target versions; the browser cannot submit or replace the plan snapshot. The input and plan are then immutable and covered by a canonical SHA-256 integrity digest. The complete record also has a checksum so a valid-looking manual edit to its mutable state fails closed. File permissions remain the authority boundary; these digests detect accidental or manual corruption, not an attacker who can replace the private record and recompute its checksums. Changing a target, channel, or gate input requires a new run. Run records live below `runtime/release-runs/`. They survive console restarts, but remain local operator state rather than a signed release artifact or the system audit log. The directory is restricted to mode `0700` and records to `0600`. Writes use a cross-process lock, a same-directory temporary file, `fsync`, atomic replacement, and directory `fsync`. Symbolic-link storage paths, overly broad record modes, malformed schemas, unknown fields, invalid state combinations, oversized files, and digest mismatches fail closed. A bad record is neither rewritten nor automatically quarantined. The default store belongs to the selected workspace: the console uses that workspace's meta checkout when it is available, or a stable workspace-digest namespace below this checkout's runtime directory otherwise. The same digest is part of the immutable input snapshot. An alternate workspace therefore cannot silently list or resume another workspace's runs. `run_state_root` remains an explicit embedding/test override. Each frozen plan step has an explicit `pending`, `running`, `succeeded`, `failed`, or `interrupted` state. Plan order remains a prerequisite: a later step is unavailable until earlier steps have succeeded. Exact attempt and resume/retry request identifiers are fingerprinted so repeated state commands are idempotent. The record keeps at most 256 server-generated state events and 128 request fingerprints. Events contain only an enum event type, timestamp, step identifier, and bounded result code—never commands, process output, confirmation text, credentials, bearer tokens, or signing material. An explicit resume after a process restart converts every persisted `running` step to `interrupted`; it never guesses whether an external effect happened. An interrupted read-only step can be prepared for retry. An interrupted mutating step remains unavailable until the operator has reconciled local and remote state and explicitly records that reconciliation when preparing the retry. A known failed attempt can likewise be prepared for retry. The UI keeps the retry control visible and explains why it is disabled when retry is unsafe. The run API is covered by the same local console token middleware as every other `/api/` route: - `POST /api/release-runs` rebuilds and freezes a selective plan. - `GET /api/release-runs` lists bounded summaries; unreadable entries are reported as unavailable instead of being silently omitted. - `GET /api/release-runs/{run_id}` reads and verifies one exact record. - `POST /api/release-runs/{run_id}/resume` records explicit recovery. - `POST /api/release-runs/{run_id}/steps/{step_id}/retry` prepares a failed or safely reconciled interrupted step for another attempt. This foundation tracks state only. It deliberately does not run a plan step or infer success from a button click. Existing preview and executor controls stay separate and continue to require `COMMIT`, `TAG`, `PUBLISH`, `APPLY`, or `PUSH` at their existing narrow boundaries. Wiring confirmed executors to claim and finish run steps, plus package/install verification, remains follow-up work; until then the console must not present a run as execution evidence. Dashboard collection is also fail closed. Unreadable Core version metadata or a malformed module contract is returned as a bounded `collection_errors` entry with a remediation, marks the dashboard blocked, and becomes a structured blocker in both full and selective release plans. The console does not silently omit a contract or turn these source errors into an HTTP 500. The release-control area above the repository table is read-only and is meant to become the central release cockpit. It shows: - local and published channel health - catalog/keyring drift - signature and trusted-key status - published module versions and refs - local checkout version drift against the catalog - catalog-declared interface compatibility The target-version control can generate the next major, minor, or subversion from the current base version. Manual target input accepts explicit versions such as `0.2.0` or `0.2.0-alpha1`, but requires the first three version numbers to move forward. Plain repository pushes are separate from catalog publication. `Preview Push` shows the selected repository push commands. `Push Selected` requires `PUSH` in the repository push confirmation field. The source release panel closes the former gap between a release plan and a catalog candidate. `Preview Tag + Publish` is non-mutating. `Create Tags` requires `TAG`; `Publish Tags` requires `PUBLISH` and atomically pushes the selected branch and annotated tag. The gate requires an aligned target version, a clean named branch with a HEAD, and a checkout that is not behind. Existing local or remote tags must resolve to the selected HEAD and are never moved. The local and remote annotated tag objects must also be identical, not merely point at the same commit. Before any source tag is created, the console loads the cross-repository module registry. This release gate also rejects every user-facing workflow documentation topic that has no scope condition, or has an alternative condition without `required_scopes` or `any_scopes`. The catalog workflow panel can also operate on the same selected rows: - `Generate` creates a signed candidate below `runtime/release-candidates/`. - `Preview` validates a candidate and shows what would be copied into the website repository. - `Apply + Website Tag` requires `APPLY` in the confirmation field. - `Push Website Release` requires `PUSH` in the confirmation field. Source release tags belong to Core or module repositories. Website catalog publication creates a separate catalog tag in the website repository; the UI names these independently to avoid confusing the two immutable references. Candidate signing is fail-closed: every Core and module source ref in the complete post-update catalog must already have the requested annotated tag both locally and on the configured source remote. Both tags must be the same annotation object with version-aligned tagged package metadata. Selected tags must additionally resolve to the selected clean, version-aligned HEAD, and the signed `selected_units` record captures the peeled commit and tag-object identifiers. Create and publish the source tags before using `Generate`. Catalog publication repeats that check for selected units and also verifies every Core and module source ref in the complete candidate, including entries preserved from the previous catalog. Historical refs need not be at the current worktree HEAD, but their local and remote annotated tags must match and their tagged installable package and module-manifest metadata must declare the catalog version. New selected releases additionally pass the stricter current-source alignment gate, including public runtime version declarations. A candidate cannot be applied or published while any referenced source tag is absent or inconsistent; the preview and API response list each repository and missing or invalid ref so the operator can repair the exact source releases first. Read-only provenance checks derive a same-host HTTPS Git URL from conventional SSH remotes when possible, with a non-interactive check of the configured remote as fallback. Source tag creation and atomic publication always use the explicit configured Git remote. The default signing key is `$HOME/.config/govoplan/release-keys/release-key-1.pem` when no signing key is entered in the UI. Generate a selective release plan from the terminal: ```sh ./.venv/bin/python tools/release/release-plan.py \ --repo govoplan-files \ --target-version 0.1.9 \ --channel stable \ --online ``` `--online` compares the local catalog/keyring with the published channel and published keyring. Use `--remote-tags` only when the plan also needs to check Git remotes for tag existence; that can be slower across the full repository set. Build a signed selective catalog candidate: ```sh KEY_DIR="$HOME/.config/govoplan/release-keys" ./.venv/bin/python tools/release/release-catalog.py selective \ --repo-version govoplan-files=0.1.9 \ --channel stable \ --catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" ``` This writes candidate catalog/keyring files below `runtime/release-candidates/`, generates the browsable module directory below `modules/`, validates the signed candidate with the module installer validator, and reports whether the candidate catalog/keyring match the currently published channel. It does not publish to the website repository. Regenerate the browsable module directory from an existing catalog/keyring: ```sh ./.venv/bin/python tools/release/release-catalog.py module-directory \ --catalog ../addideas-govoplan-website/public/catalogs/v1/channels/stable.json \ --keyring ../addideas-govoplan-website/public/catalogs/v1/keyring.json \ --output-dir runtime/module-directory-preview \ --channel stable ``` Preview publication of a reviewed candidate: ```sh ./.venv/bin/python tools/release/release-catalog.py publish-candidate \ --candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --channel stable ``` Apply the reviewed candidate into the website repository without pushing: ```sh ./.venv/bin/python tools/release/release-catalog.py publish-candidate \ --candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --channel stable \ --apply \ --commit \ --tag ``` Push is a separate explicit flag: ```sh ./.venv/bin/python tools/release/release-catalog.py publish-candidate \ --candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --channel stable \ --apply \ --commit \ --tag \ --push ``` Published channels are expected below the public catalog base URL: - `https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json` - `https://govoplan.add-ideas.de/catalogs/v1/keyring.json` The console treats channels as release artifacts. A package can advance without forcing every repository to the same tag, but channel publication must preserve the unchanged package versions, validate interface compatibility, sign the updated catalog, and keep the published keyring healthy. When a selected module exposes a WebUI package, its requested version must also match Core's `webui/package.release.json` input and the resolved `package-lock.release.json` entry. The source-tag preflight, selective plan, and catalog-candidate writer all enforce this composition boundary. Pins for modules that are not part of the selective release remain unchanged. Release integration also enforces repository and composition version alignment and generates a CycloneDX SBOM from the resolved Python environment and the release WebUI lockfile. Catalog publication should attach that immutable SBOM and its digest to the corresponding Core/composition release. Addresses and Notifications have current WebUI source contributions but are not part of the pinned v0.1.8 WebUI release composition because their v0.1.8 tags predate those packages. They re-enter the release composition only through new, immutable module tags whose backend, manifest, frontend, and lock metadata pass the alignment gate. Candidate publication verifies signatures against the already published keyring, not against keys supplied only by the candidate. A changed keyring must have its canonical SHA-256 embedded in the signed catalog. The public module-directory files are regenerated from that verified catalog and keyring at publication time; candidate-supplied directory files are never copied as authoritative provenance. ## Published Module Directory The target release repository is an online, browsable module directory. The catalog remains the machine-readable channel entry point, but the published space should also expose a tree that operators and installations can inspect: - `/catalogs/v1/channels/.json` describes the active channel state. - `/catalogs/v1/keyring.json` publishes trusted release signing keys. - `/catalogs/v1/modules///manifest.json` describes one published module version, including repository refs, package refs, contracts, compatibility windows, signatures, and available artifacts. - `/catalogs/v1/modules//index.json` lists available versions for one module. - `/catalogs/v1/modules/index.json` lists all published modules. Gitea tags/releases remain the source release anchors. The public GovOPlaN catalog directory becomes the installation-facing release repository that points to those anchors and carries structured compatibility information. Selective catalog candidates now include this directory tree. Publishing a candidate copies the channel catalog, keyring, and `modules/` tree into the website repository together. Selective catalog generation can add a module that is not yet represented in the source channel. Initial entries are synthesized from the selected repository's `[project]` metadata, `govoplan.modules` entry point, and runtime `ModuleManifest`; there is no separate hand-maintained initial-entry list. The release gate requires the entry point to resolve inside the selected checkout, exact package/manifest/frontend version agreement, verified source-tag provenance, a non-conflicting module id, and complete required dependency and interface closure in the resulting candidate. Classification beyond the manifest-derived `official` tag remains an explicit later catalog curation step rather than an inferred business/service label. ## Direction The console should grow in slices: 1. Read-only dashboard and next-action suggestions. 2. Release plan builder that writes explicit JSON plans. 3. Dry-run executor that shows exact commands and expected file changes. 4. Apply executor for commit, tag, push, artifact build, signing, and catalog publication. 5. Compatibility planner for manifest contracts and version ranges. 6. Install/test workflow that validates a release from tags or catalog entries. All mutation must stay explicit: plan first, dry run second, apply only after a clear confirmation.