From ce5528e3b821e3fa1c138b2854f96e8a6083c4c7 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Mon, 3 Aug 2026 20:17:29 +0200 Subject: [PATCH] Record first verified runtime distribution --- ...NSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md | 37 +++++++++++++------ ...STEM_ADMINISTRATOR_LIFECYCLE_USER_STORY.md | 16 +++++--- tests/test_runtime_distribution.py | 8 ++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/INSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md b/docs/INSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md index 5502316..8c46f18 100644 --- a/docs/INSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md +++ b/docs/INSTALLATION_AND_DEPLOYMENT_ARCHITECTURE.md @@ -221,26 +221,39 @@ references and archive hashes; mutable tags or incomplete bundles are rejected. ## Current Production Gates -The tool deliberately reports blockers instead of pretending the source tree is -a production distribution: +The first immutable production-distribution baseline is published as +[`v0.1.14`](https://git.add-ideas.de/GovOPlaN/govoplan/releases/tag/v0.1.14) +from source commit `1f039dd39c1ce2672f4978c8abc6dff862ef1445`. Runtime +Distribution [run #459](https://git.add-ideas.de/GovOPlaN/govoplan/actions/runs/459) +proved migrations, schema compatibility, non-root API/Web readiness, and worker +delivery/shutdown on both `linux/amd64` and `linux/arm64`. Its signed manifest +has SHA-256 +`d703267e01855dee63200cb20921c91c3f95fbff550c8ca76e9a35cba3f69109` +and pins these runtime indexes: -1. **First publication.** The protected workflow and fail-closed artifact - contracts are implemented, but a release operator must configure the Gitea - registry/release tokens and runtime Ed25519 key, publish the first pinned - release, and retain its amd64/arm64 readiness evidence. -3. **First administrator.** Production needs a one-time, restricted enrollment +- API: `git.add-ideas.de/govoplan/runtime-api@sha256:197ed01790986f2bc927eaa5d8348fa118702e5d2dc05feb851fc2643c23764a` +- WebUI: `git.add-ideas.de/govoplan/runtime-web@sha256:e936cca124f1fad29a067834cf17627d4c236410fdc3fa129e0ccb26b8193812` + +The signed bootstrap has SHA-256 +`1ff946fba82b0895d153b23352d06e30fe18388450dfd37fed6fb9912310efc5` +and key id `runtime-distribution-2026-01`. The managed-ingress boundary passed +the same publication run and the independently dispatchable Runtime Ingress +Drill [run #458](https://git.add-ideas.de/GovOPlaN/govoplan/actions/runs/458). +Every later release must renew this evidence; the following target-specific +gates remain: + +1. **First administrator.** Production needs a one-time, restricted enrollment identity. The development bootstrap must not be enabled in production. -4. **Image/module composition.** The deployer now enforces the signed +2. **Image/module composition.** The deployer enforces the signed composition. A selected module not shipped by that release cannot be enabled. -5. **Deployment agent.** Web updates need a separate privileged reconciler with +3. **Deployment agent.** Web updates need a separate privileged reconciler with a typed command allowlist. The API and browser must never receive the Docker socket or arbitrary shell access. -6. **Ingress reachability evidence.** Managed Caddy ingress and the +4. **Target reachability evidence.** Managed Caddy ingress and the existing-proxy contract are implemented. A production claim still requires running `doctor` from the target host after public DNS/firewall changes and - retaining the first successful container drill and public TLS/readiness - evidence. + retaining public TLS/readiness evidence for that deployment. `apply --allow-unverified-images` is therefore restricted to the evaluation profile. It explicitly acknowledges both mutable image identities and diff --git a/docs/SYSTEM_ADMINISTRATOR_LIFECYCLE_USER_STORY.md b/docs/SYSTEM_ADMINISTRATOR_LIFECYCLE_USER_STORY.md index f763215..714804e 100644 --- a/docs/SYSTEM_ADMINISTRATOR_LIFECYCLE_USER_STORY.md +++ b/docs/SYSTEM_ADMINISTRATOR_LIFECYCLE_USER_STORY.md @@ -141,12 +141,16 @@ The canonical backlog item is Implementation status as of the current source tree: -- Slice 1 now has the source-controlled production artifact boundary: offline - per-architecture wheel resolution, non-root API/Web image definitions, - multi-architecture OCI publication, signed composition/SBOM/provenance, - immutable Gitea assets, a signed one-file deployer, and fail-closed manifest - adoption. The first real published release and cross-architecture runtime - evidence remain release-operator work rather than source-code claims. +- Slice 1 has a published production-artifact baseline. Immutable + [`v0.1.14`](https://git.add-ideas.de/GovOPlaN/govoplan/releases/tag/v0.1.14) + binds source commit `1f039dd39c1ce2672f4978c8abc6dff862ef1445`, a signed + one-file deployer, exact API/Web and managed-dependency image digests, + composition, SBOMs, and provenance. Runtime Distribution + [run #459](https://git.add-ideas.de/GovOPlaN/govoplan/actions/runs/459) + passed migrations, schema checks, non-root API/Web readiness, and worker + delivery/shutdown on both amd64 and arm64. Each future release must renew the + evidence, and a real installation must still produce topology-specific + ingress, failover, backup, and recovery receipts. - Slice 6 has a working application-tier foundation: state profiles, shared object storage, runtime node registration/heartbeats/drain, fenced scheduler, migration serialization, exact-head startup waiting, Ops visibility, and a diff --git a/tests/test_runtime_distribution.py b/tests/test_runtime_distribution.py index def7d0a..12f8848 100644 --- a/tests/test_runtime_distribution.py +++ b/tests/test_runtime_distribution.py @@ -93,6 +93,14 @@ class RuntimeDistributionTests(unittest.TestCase): with self.assertRaisesRegex(DistributionError, "active trusted key"): verify_manifest(unknown, self.keyring, now=self.now) + with self.assertRaisesRegex(DistributionError, "expected 'candidate'"): + verify_manifest( + self._manifest(), + self.keyring, + expected_channel="candidate", + now=self.now, + ) + def test_offline_image_index_is_complete_and_digest_bound(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-offline-images-") as value: root = Path(value)