Prepare durable isolated Vergabe pilot deployment and recovery
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
eff457ef52
commit
959eba637f
11 changed files with 408 additions and 9 deletions
|
|
@ -4,7 +4,7 @@ description: |
|
|||
Vergabe Teilnahme — internal Django tender/bid management web app.
|
||||
Single-instance v1 deployment; HA and canary are deferred.
|
||||
type: application
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
appVersion: "0.1.0"
|
||||
keywords:
|
||||
- django
|
||||
|
|
|
|||
|
|
@ -21,8 +21,34 @@ app.kubernetes.io/instance: {{ .Release.Name }}
|
|||
{{- end -}}
|
||||
|
||||
{{- define "vergabe.image" -}}
|
||||
{{- if .Values.image.digest -}}
|
||||
{{- if not (regexMatch "^sha256:[a-f0-9]{64}$" .Values.image.digest) -}}
|
||||
{{- fail "image.digest must be a sha256 OCI digest" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
|
||||
{{- else -}}
|
||||
{{- if not .Values.image.tag -}}
|
||||
{{- fail "image.tag is required — pin it in helm/vergabe-teilnahme-values.yaml" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "vergabe.validatePilot" -}}
|
||||
{{- if .Values.pilot.enabled -}}
|
||||
{{- if ne (int .Values.replicaCount) 1 -}}
|
||||
{{- fail "invited pilot requires exactly one application replica" -}}
|
||||
{{- end -}}
|
||||
{{- if or (not .Values.persistence.media.enabled) (not .Values.persistence.appState.enabled) -}}
|
||||
{{- fail "invited pilot requires persistent media and appState" -}}
|
||||
{{- end -}}
|
||||
{{- if not .Values.image.digest -}}
|
||||
{{- fail "invited pilot requires an immutable image.digest" -}}
|
||||
{{- end -}}
|
||||
{{- $mediaClaim := default (printf "%s-media" (include "vergabe.fullname" .)) .Values.persistence.media.existingClaim -}}
|
||||
{{- $stateClaim := default (printf "%s-app-state" (include "vergabe.fullname" .)) .Values.persistence.appState.existingClaim -}}
|
||||
{{- if eq $mediaClaim $stateClaim -}}
|
||||
{{- fail "media and appState must use distinct claims; operational state must not be downloadable" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{{- include "vergabe.validatePilot" . }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
|
|
@ -8,10 +9,14 @@ spec:
|
|||
selector:
|
||||
matchLabels: {{- include "vergabe.selectorLabels" . | nindent 6 }}
|
||||
strategy:
|
||||
{{- if or .Values.persistence.media.enabled .Values.persistence.appState.enabled }}
|
||||
type: Recreate
|
||||
{{- else }}
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
{{- end }}
|
||||
template:
|
||||
metadata:
|
||||
labels: {{- include "vergabe.selectorLabels" . | nindent 8 }}
|
||||
|
|
@ -59,16 +64,29 @@ spec:
|
|||
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
||||
{{- end }}
|
||||
resources: {{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- if .Values.persistence.media.enabled }}
|
||||
{{- if or .Values.persistence.media.enabled .Values.persistence.appState.enabled }}
|
||||
volumeMounts:
|
||||
{{- if .Values.persistence.media.enabled }}
|
||||
- name: media
|
||||
mountPath: /app/media
|
||||
{{- end }}
|
||||
{{- if .Values.persistence.appState.enabled }}
|
||||
- name: app-state
|
||||
mountPath: /app/.issue-facade
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.persistence.media.enabled }}
|
||||
{{- if or .Values.persistence.media.enabled .Values.persistence.appState.enabled }}
|
||||
volumes:
|
||||
{{- if .Values.persistence.media.enabled }}
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "vergabe.fullname" . }}-media
|
||||
claimName: {{ default (printf "%s-media" (include "vergabe.fullname" .)) .Values.persistence.media.existingClaim }}
|
||||
{{- end }}
|
||||
{{- if .Values.persistence.appState.enabled }}
|
||||
- name: app-state
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ default (printf "%s-app-state" (include "vergabe.fullname" .)) .Values.persistence.appState.existingClaim }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector: {{- toYaml . | nindent 8 }}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ spec:
|
|||
protocol: TCP
|
||||
name: http
|
||||
selector: {{- include "vergabe.selectorLabels" . | nindent 4 }}
|
||||
{{- if .Values.persistence.media.enabled }}
|
||||
{{- if and .Values.persistence.media.enabled (not .Values.persistence.media.existingClaim) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "vergabe.fullname" . }}-media
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels: {{- include "vergabe.labels" . | nindent 4 }}
|
||||
spec:
|
||||
storageClassName: {{ .Values.persistence.media.storageClass }}
|
||||
|
|
@ -25,3 +27,19 @@ spec:
|
|||
requests:
|
||||
storage: {{ .Values.persistence.media.size }}
|
||||
{{- end }}
|
||||
{{- if and .Values.persistence.appState.enabled (not .Values.persistence.appState.existingClaim) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "vergabe.fullname" . }}-app-state
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
labels: {{- include "vergabe.labels" . | nindent 4 }}
|
||||
spec:
|
||||
storageClassName: {{ .Values.persistence.appState.storageClass }}
|
||||
accessModes: [{{ .Values.persistence.appState.accessMode }}]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.appState.size }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/vergabe-teilnahme
|
||||
tag: "" # required; pinned via helm/vergabe-teilnahme-values.yaml
|
||||
digest: "" # preferred; takes precedence over tag
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
replicaCount: 1 # v1 is single-instance; HA is deferred (RAILIANCE-WP-0002 Notes)
|
||||
|
||||
# An invited company pilot requires an immutable image and both data volumes.
|
||||
pilot:
|
||||
enabled: false
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
|
@ -19,8 +24,7 @@ resources:
|
|||
memory: 1Gi
|
||||
|
||||
# Env from the K8s Secret created out-of-band (vergabe-teilnahme-env).
|
||||
# Holds SECRET_KEY + DATABASE_URL. Created by the operator with kubectl
|
||||
# create secret generic vergabe-teilnahme-env --from-literal=...
|
||||
# Holds SECRET_KEY + DATABASE_URL. Deliver via the admitted platform custody lane.
|
||||
envSecretName: vergabe-teilnahme-env
|
||||
|
||||
# Non-secret env injected directly into the Deployment.
|
||||
|
|
@ -45,14 +49,21 @@ probes:
|
|||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
# PVC for media uploads is deferred — Django MEDIA is in-pod ephemeral
|
||||
# for v1. Switch to true + a storageClass once media uploads land.
|
||||
# Existing installations remain opt-in. Pilot mode refuses ephemeral state.
|
||||
# PVCs are retained on Helm uninstall; a verified off-host backup is still required.
|
||||
persistence:
|
||||
media:
|
||||
enabled: false
|
||||
storageClass: local-path
|
||||
size: 5Gi
|
||||
accessMode: ReadWriteOnce
|
||||
existingClaim: ""
|
||||
appState:
|
||||
enabled: false
|
||||
storageClass: local-path
|
||||
size: 1Gi
|
||||
accessMode: ReadWriteOnce
|
||||
existingClaim: ""
|
||||
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
|
|
|
|||
12
docs/evidence/2026-09-11-vergabe-pilot-inventory.json
Normal file
12
docs/evidence/2026-09-11-vergabe-pilot-inventory.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"schema": "railiance.vergabe-pilot-inventory.v1",
|
||||
"observed_at": "2026-09-11T14:27:23.565171+00:00",
|
||||
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"historical_namespace_present": false,
|
||||
"matching_deployments": [],
|
||||
"query_scope": "Deployment names/images on connected cluster only; databases and other hosts not inventoried",
|
||||
"live_mutations": 0,
|
||||
"customer_data_read": false,
|
||||
"credentials_read": false,
|
||||
"claim": "Historical runbook does not establish current deployment; no data-loss conclusion."
|
||||
}
|
||||
96
docs/vergabe-teilnahme-pilot.md
Normal file
96
docs/vergabe-teilnahme-pilot.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Invited company pilot
|
||||
|
||||
User decision, 2026-09-11: one company, several users, manual onboarding; pricing
|
||||
later. VERGABE-WP-0019 owns product acceptance. RAPPS-WP-0014 owns placement,
|
||||
release and recovery. This is a review and execution contract, not a claim that
|
||||
the historical deployment is still live.
|
||||
|
||||
## Exact release packet
|
||||
|
||||
Before native admission, record the following non-secret values in the company
|
||||
binding. Company/host/data selection is pending; do not treat example values as
|
||||
an assigned tenant.
|
||||
|
||||
| Binding | Required evidence |
|
||||
| --- | --- |
|
||||
| Company and data | Named company contact, expected users, empty workspace or explicit import source/owner |
|
||||
| Placement | Cluster UID, dedicated namespace/release, admitted operator and resource capacity |
|
||||
| Release | Login-protected source commit, live CI result, published OCI digest, chart commit/version |
|
||||
| Public route | Assigned HTTPS hostname, TLS receipt, ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, edge login abuse control |
|
||||
| Custody | Dedicated database/role, runtime Secret reference and platform delivery receipt; no values in the packet |
|
||||
| Durable data | Media PVC and distinct issue-facade state PVC, database backup owner, storage class/capacity |
|
||||
| Recovery/support | Consistent off-host backup, isolated restore and rollback receipt, cadence/retention, incident contact |
|
||||
|
||||
Use `helm/vergabe-teilnahme-pilot-values.example.yaml` as the starting point.
|
||||
It intentionally has no image digest and cannot render until one is supplied.
|
||||
The old published image `main-fa9f082` does not contain the new access gate.
|
||||
Do not promote it merely because the older source suite passed.
|
||||
|
||||
```sh
|
||||
helm lint charts/vergabe-teilnahme -f path/to/reviewed-company-values.yaml
|
||||
helm template vergabe-teilnahme charts/vergabe-teilnahme \
|
||||
--namespace reviewed-company-namespace -f path/to/reviewed-company-values.yaml
|
||||
python3 -m unittest discover -s tests -p 'test_vergabe_pilot_chart.py'
|
||||
```
|
||||
|
||||
The default resource names are intentionally unchanged; company isolation is by
|
||||
separate namespace/database/claims. Do not install two company releases in the
|
||||
same namespace. Resolve the registry digest before setting `image.digest`.
|
||||
Run the existing owner-approved server dry-run and deployment lane against the
|
||||
reviewed placement after its packet is complete.
|
||||
|
||||
## Persistence and recovery
|
||||
|
||||
Mount media at `/app/media` and issue state at `/app/.issue-facade` on distinct
|
||||
claims. Never expose the issue database through MEDIA_ROOT, ingress file serving
|
||||
or an object bucket used for public assets. Production document downloads route
|
||||
through Django authentication. All active users belong to this single company;
|
||||
there is no per-tender ACL or shared-application tenant discriminator in v1.
|
||||
|
||||
The chart uses Recreate with persistence to avoid overlapping old/new pods and
|
||||
RWO attachment conflicts. UID/GID/fsGroup 999 match the runtime image. Helm
|
||||
uninstall retains claims; namespace deletion or node loss can still destroy
|
||||
local-path data. Retention annotations are not a backup.
|
||||
|
||||
Before customer data is admitted, create a synthetic tender, lot, task, uploaded
|
||||
document and issue-facade record; capture identifiers/content checksums. Replace
|
||||
the application pod and verify all data and both user accounts remain usable.
|
||||
For a coherent backup, quiesce application writes through the operator's
|
||||
maintenance procedure, capture PostgreSQL plus media and a consistent SQLite
|
||||
backup (including any required journal state), and retain the matched recovery
|
||||
point off-host. Restore to an isolated database and new claims; `existingClaim`
|
||||
can select those restored claims. Repeat login, document checksum and workflow
|
||||
checks there. Record elapsed recovery time, backup age, image/schema revision
|
||||
and operator. Never rehearse by overwriting historical `vergabe_db`.
|
||||
|
||||
Inspect every release's migrations before rollback; reverting an image alone
|
||||
cannot undo an incompatible schema or restore lost data. Record the previously
|
||||
accepted digest and demonstrated data recovery path. Short planned interruption
|
||||
is acceptable for the invited pilot once the operator and company contact agree;
|
||||
HA is not claimed.
|
||||
|
||||
## Manual onboarding and acceptance
|
||||
|
||||
Use the existing Django administration via the admitted operator path to create
|
||||
ordinary active members. Keep staff/superuser access with the designated
|
||||
operator. Supply initial credentials over the existing private human channel;
|
||||
never paste them into Git, State Hub, command arguments or chat. Members can
|
||||
change passwords in the UI; operators handle reset and deactivation manually.
|
||||
Do not run `seed_dev` on a pilot/customer database.
|
||||
|
||||
Before admitting the first users, verify HTTPS login, CSRF failure behavior,
|
||||
anonymous document refusal, two separate user sessions and deactivation of an
|
||||
already logged-in account. Complete the tender → lot → task/document → domain
|
||||
approval → submission workflow and feedback with the company contact. Customer
|
||||
support contact, incident routing and backup responsibility must be recorded.
|
||||
Pricing, automated invitation email, SSO and shared tenancy can be considered
|
||||
later; none is implied by this initial pilot contract.
|
||||
|
||||
## Current inventory — 2026-09-11
|
||||
|
||||
Read-only checks on the connected Railiance cluster found no namespace
|
||||
`vergabe-teilnahme` and no Deployment whose name/image contains vergabe or
|
||||
teilnahme. The older `docs/vergabe-teilnahme.md` is historical deployment and
|
||||
recovery evidence, not proof of current placement. Database contents and other
|
||||
hosts have not been inventoried; no data-loss conclusion follows from namespace
|
||||
absence. RAPPS-WP-0014-T02 retains that exact inventory/admission step.
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
> Current status, 2026-09-11: this is historical deployment evidence. The checked
|
||||
> Railiance cluster has no `vergabe-teilnahme` namespace or matching Deployment.
|
||||
> Use [the invited-pilot contract](vergabe-teilnahme-pilot.md) and RAPPS-WP-0014
|
||||
> for fresh placement, release, custody and recovery. Do not execute the older
|
||||
> credential/bootstrap recipes against an unverified target.
|
||||
|
||||
# vergabe-teilnahme — operator runbook
|
||||
|
||||
Production deployment of the Django tender-management app, shipped
|
||||
|
|
|
|||
20
helm/vergabe-teilnahme-pilot-values.example.yaml
Normal file
20
helm/vergabe-teilnahme-pilot-values.example.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Review-only template. Copy into the exact admitted company binding, fill the
|
||||
# tested image digest/host/Secret, and use a dedicated namespace and database.
|
||||
# This does not upgrade or reuse the historical vergabe_db installation.
|
||||
pilot:
|
||||
enabled: true
|
||||
image:
|
||||
digest: "" # Required; the old published image lacks the pilot access gate.
|
||||
replicaCount: 1
|
||||
envSecretName: vergabe-pilot-env
|
||||
env:
|
||||
DJANGO_SETTINGS_MODULE: vergabe_teilnahme.settings.prod
|
||||
ALLOWED_HOSTS: pilot.example.invalid,localhost
|
||||
CSRF_TRUSTED_ORIGINS: https://pilot.example.invalid
|
||||
probes:
|
||||
hostHeader: pilot.example.invalid
|
||||
persistence:
|
||||
media:
|
||||
enabled: true
|
||||
appState:
|
||||
enabled: true
|
||||
110
tests/test_vergabe_pilot_chart.py
Normal file
110
tests/test_vergabe_pilot_chart.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"""Render-level checks for data durability and separation; no cluster mutations.
|
||||
|
||||
Run with: python3 -m unittest discover -s tests -p 'test_vergabe_pilot_chart.py'
|
||||
Requires Helm and PyYAML.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
CHART = Path(__file__).resolve().parents[1] / 'charts/vergabe-teilnahme'
|
||||
DIGEST = 'sha256:' + 'a' * 64
|
||||
|
||||
|
||||
def render(values):
|
||||
return subprocess.run(
|
||||
['helm', 'template', 'pilot', str(CHART), '-n', 'pilot-test', '-f', '-'],
|
||||
input=yaml.safe_dump(values), text=True, capture_output=True, check=False,
|
||||
)
|
||||
|
||||
|
||||
def pilot():
|
||||
return {
|
||||
'pilot': {'enabled': True},
|
||||
'image': {'digest': DIGEST},
|
||||
'persistence': {'media': {'enabled': True}, 'appState': {'enabled': True}},
|
||||
}
|
||||
|
||||
|
||||
class PilotChartTests(unittest.TestCase):
|
||||
def objects(self, values):
|
||||
result = render(values)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
return list(yaml.safe_load_all(result.stdout))
|
||||
|
||||
def test_existing_tag_profile_retains_original_rollout(self):
|
||||
objects = self.objects({'image': {'tag': 'legacy-test'}})
|
||||
deployment = next(o for o in objects if o['kind'] == 'Deployment')
|
||||
self.assertEqual(deployment['spec']['strategy']['type'], 'RollingUpdate')
|
||||
self.assertFalse(any(o['kind'] == 'PersistentVolumeClaim' for o in objects))
|
||||
|
||||
def test_pilot_pins_image_and_separates_durable_state(self):
|
||||
objects = self.objects(pilot())
|
||||
deployment = next(o for o in objects if o['kind'] == 'Deployment')
|
||||
claims = {o['metadata']['name']: o for o in objects
|
||||
if o['kind'] == 'PersistentVolumeClaim'}
|
||||
self.assertEqual(len(claims), 2)
|
||||
self.assertEqual(deployment['spec']['strategy'], {'type': 'Recreate'})
|
||||
self.assertEqual(deployment['spec']['replicas'], 1)
|
||||
pod = deployment['spec']['template']['spec']
|
||||
app = pod['containers'][0]
|
||||
self.assertTrue(app['image'].endswith('@' + DIGEST))
|
||||
self.assertEqual({v['name']: v['mountPath'] for v in app['volumeMounts']},
|
||||
{'media': '/app/media', 'app-state': '/app/.issue-facade'})
|
||||
for volume in pod['volumes']:
|
||||
claim = claims[volume['persistentVolumeClaim']['claimName']]
|
||||
self.assertEqual(claim['metadata']['annotations']['helm.sh/resource-policy'], 'keep')
|
||||
self.assertEqual(pod['securityContext']['fsGroup'], 999)
|
||||
|
||||
def test_pilot_refuses_each_ephemeral_store(self):
|
||||
for store in ('media', 'appState'):
|
||||
with self.subTest(store=store):
|
||||
values = pilot()
|
||||
values['persistence'][store]['enabled'] = False
|
||||
result = render(values)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('persistent media and appState', result.stderr)
|
||||
|
||||
def test_pilot_refuses_mutable_image_and_bad_digest(self):
|
||||
for image in ({'tag': 'latest', 'digest': ''}, {'digest': 'sha256:invalid'}):
|
||||
with self.subTest(image=image):
|
||||
values = pilot()
|
||||
values['image'] = image
|
||||
self.assertNotEqual(render(values).returncode, 0)
|
||||
|
||||
def test_pilot_refuses_multiple_replicas(self):
|
||||
values = pilot()
|
||||
values['replicaCount'] = 2
|
||||
result = render(values)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('exactly one', result.stderr)
|
||||
|
||||
def test_restored_claims_are_reused_without_recreation(self):
|
||||
values = pilot()
|
||||
for store in ('media', 'appState'):
|
||||
values['persistence'][store]['existingClaim'] = 'restored-' + store.lower()
|
||||
objects = self.objects(values)
|
||||
self.assertFalse(any(o['kind'] == 'PersistentVolumeClaim' for o in objects))
|
||||
deployment = next(o for o in objects if o['kind'] == 'Deployment')
|
||||
volumes = deployment['spec']['template']['spec']['volumes']
|
||||
self.assertEqual({v['persistentVolumeClaim']['claimName'] for v in volumes},
|
||||
{'restored-media', 'restored-appstate'})
|
||||
|
||||
def test_pilot_refuses_operational_state_in_download_volume(self):
|
||||
for media, state in (('shared-data', 'shared-data'),
|
||||
('vergabe-teilnahme-app-state', ''),
|
||||
('', 'vergabe-teilnahme-media')):
|
||||
with self.subTest(media=media, state=state):
|
||||
values = pilot()
|
||||
values['persistence']['media']['existingClaim'] = media
|
||||
values['persistence']['appState']['existingClaim'] = state
|
||||
result = render(values)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn('distinct claims', result.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
82
workplans/RAPPS-WP-0014-vergabe-invited-pilot.md
Normal file
82
workplans/RAPPS-WP-0014-vergabe-invited-pilot.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
id: RAPPS-WP-0014
|
||||
type: workplan
|
||||
title: "Deploy and recover the first invited Vergabe company pilot"
|
||||
domain: financials
|
||||
repo: railiance-apps
|
||||
status: active
|
||||
owner: the-custodian
|
||||
topic_slug: railiance
|
||||
created: "2026-09-11"
|
||||
updated: "2026-09-11"
|
||||
related: [VERGABE-WP-0019, VERGABE-WP-0018, HFACT-WP-0001]
|
||||
---
|
||||
|
||||
# Invited Vergabe pilot on Railiance
|
||||
|
||||
## Prepare a durable and immutable single-company chart
|
||||
|
||||
```task
|
||||
id: RAPPS-WP-0014-T01
|
||||
status: done
|
||||
priority: high
|
||||
assignee: the-custodian
|
||||
```
|
||||
|
||||
Extend the existing media PVC support with a distinct issue-facade state PVC,
|
||||
optional existing claims for restore, retained claims on Helm uninstall and
|
||||
Recreate rollout when persistent local state is mounted. Pilot mode requires
|
||||
one replica, both durable stores, distinct claims and a valid OCI image digest.
|
||||
The legacy opt-in behavior remains available for non-pilot installations.
|
||||
Seven render regression tests and Helm lint pass. Chart 0.2.0 and the review-only
|
||||
values template prepare the deployment; no live resources were changed.
|
||||
|
||||
## Bind the exact company, release, placement, data and access
|
||||
|
||||
```task
|
||||
id: RAPPS-WP-0014-T02
|
||||
status: progress
|
||||
priority: high
|
||||
assignee: the-custodian
|
||||
```
|
||||
|
||||
Consume VERGABE-WP-0019-T02's login-protected release after live CI/publication.
|
||||
Record exact image/chart revision, company, user count, host/TLS, dedicated
|
||||
namespace, database/role, both PVCs and admitted runtime Secret custody. Obtain
|
||||
the user's empty-vs-existing-data disposition. Existing vergabe_db is not test
|
||||
data. Resolve target inventory before using historical runbook names: the
|
||||
checked Railiance cluster has no vergabe-teilnahme namespace or matching
|
||||
Deployment on 2026-09-11. Do not infer data loss or authorization to recreate it.
|
||||
|
||||
Use `docs/vergabe-teilnahme-pilot.md` for the review packet. Secret creation,
|
||||
operator access and placement consume existing platform lanes; they do not
|
||||
create a parallel identity framework. The public edge needs an admitted login
|
||||
abuse-control policy and TLS; Django's authentication gate alone is not a rate
|
||||
limiter. Keep `/media/` behind the app gate. Exact customer identity/host/data
|
||||
selection is pending user input; source preparation can proceed meanwhile.
|
||||
|
||||
## Demonstrate restart, isolated restore, rollback and operating ownership
|
||||
|
||||
```task
|
||||
id: RAPPS-WP-0014-T03
|
||||
status: wait
|
||||
priority: high
|
||||
assignee: the-custodian
|
||||
depends_on: [RAPPS-WP-0014-T02]
|
||||
blocking_reason: "Await exact placement/release/data binding before native rehearsal and admission."
|
||||
```
|
||||
|
||||
With synthetic fixture data on the admitted deployment, prove login and health,
|
||||
two-user collaboration, document upload/download and issue state across pod
|
||||
replacement. Establish a consistent recovery point for PostgreSQL, media and
|
||||
issue-facade SQLite; rehearse recovery into a separate database and separate
|
||||
claims, repeat the workflow, and record recovery time and checksums without
|
||||
customer content. Record backup owner/cadence/retention/off-host destination,
|
||||
restore command revision and monitoring/incident owner. Retained local-path
|
||||
PVCs are neither off-host backup nor node-failure protection.
|
||||
|
||||
Review upgrade/migration effects and exact rollback image/data handling. A
|
||||
single-writer Recreate release has a short service interruption; do not promise
|
||||
HA. Return evidence to VERGABE-WP-0019-T03/T04 before customer invitations.
|
||||
Native factory-produced delivery remains VERGABE-WP-0018/HFACT-WP-0001's separate
|
||||
claim. Pricing and shared-app tenancy are outside this invited-pilot milestone.
|
||||
Loading…
Add table
Add a link
Reference in a new issue