diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index c2e563f..a717f76 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -12,6 +12,7 @@ | workplan | RAPPS-WP-0002 | finished | — | workplans/RAPPS-WP-0002-railiance01-activity-core-backup-automation.md | | workplan | RAPPS-WP-0003 | finished | — | workplans/RAPPS-WP-0003-policy-nexus-production-binding.md | | workplan | RAPPS-WP-0014 | blocked | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md | +| workplan | RAPPS-WP-0015 | active | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md | | task | RAPPS-WP-0001-T01 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md | | task | RAPPS-WP-0001-T02 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md | | task | RAPPS-WP-0001-T03 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md | @@ -31,3 +32,7 @@ | task | RAPPS-WP-0014-T01 | done | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md | | task | RAPPS-WP-0014-T02 | done | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md | | task | RAPPS-WP-0014-T03 | wait | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md | +| task | RAPPS-WP-0015-T01 | progress | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md | +| task | RAPPS-WP-0015-T02 | progress | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md | +| task | RAPPS-WP-0015-T03 | wait | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md | +| task | RAPPS-WP-0015-T04 | wait | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md | diff --git a/charts/coulomb-social/Chart.yaml b/charts/coulomb-social/Chart.yaml index 89acef2..13bf18f 100644 --- a/charts/coulomb-social/Chart.yaml +++ b/charts/coulomb-social/Chart.yaml @@ -1,10 +1,10 @@ apiVersion: v2 name: coulomb-social description: | - coulomb.social — co-creation platform (NetKingdom identity shell). - Standalone Django app; content migration deferred. + coulomb.social — co-creation workspace with NetKingdom identity. + Django app with persistent single-writer Markdown content. type: application -version: 0.1.0 +version: 0.2.0 appVersion: "0.1.0" keywords: - django diff --git a/charts/coulomb-social/templates/_helpers.tpl b/charts/coulomb-social/templates/_helpers.tpl index 7016831..1e83287 100644 --- a/charts/coulomb-social/templates/_helpers.tpl +++ b/charts/coulomb-social/templates/_helpers.tpl @@ -18,8 +18,15 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- define "coulomb.image" -}} +{{- if .Values.image.digest -}} +{{- if not (regexMatch "^sha256:[a-f0-9]{64}$" .Values.image.digest) -}} +{{- fail "image.digest must be a sha256 registry 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/coulomb-social-values.yaml" -}} {{- end -}} {{- printf "%s:%s" .Values.image.repository .Values.image.tag -}} {{- end -}} +{{- end -}} diff --git a/charts/coulomb-social/templates/content-pvc.yaml b/charts/coulomb-social/templates/content-pvc.yaml new file mode 100644 index 0000000..88120b3 --- /dev/null +++ b/charts/coulomb-social/templates/content-pvc.yaml @@ -0,0 +1,17 @@ +{{- if .Values.persistence.content.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "coulomb.fullname" . }}-content + labels: {{- include "coulomb.labels" . | nindent 4 }} + {{- if .Values.persistence.content.retain }} + annotations: + helm.sh/resource-policy: keep + {{- end }} +spec: + accessModes: [ReadWriteOnce] + storageClassName: {{ .Values.persistence.content.storageClass | quote }} + resources: + requests: + storage: {{ .Values.persistence.content.size | quote }} +{{- end }} diff --git a/charts/coulomb-social/templates/deployment.yaml b/charts/coulomb-social/templates/deployment.yaml index b4657e1..d9ad6f0 100644 --- a/charts/coulomb-social/templates/deployment.yaml +++ b/charts/coulomb-social/templates/deployment.yaml @@ -7,11 +7,22 @@ spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: {{- include "coulomb.selectorLabels" . | nindent 6 }} + {{- if and .Values.persistence.content.enabled (ne (int .Values.replicaCount) 1) }} + {{- fail "Persistent content requires replicaCount=1 (single writer)" }} + {{- end }} + {{- if and .Values.persistence.content.enabled (ne .Values.deploymentStrategy "Recreate") }} + {{- fail "Persistent content requires Recreate strategy" }} + {{- end }} + {{- if and .Values.persistence.content.enabled (ne (index .Values.env "CONTENT_ROOT") .Values.persistence.content.mountPath) }} + {{- fail "CONTENT_ROOT must match the persistent content mountPath" }} + {{- end }} strategy: - type: RollingUpdate + type: {{ .Values.deploymentStrategy }} + {{- if eq .Values.deploymentStrategy "RollingUpdate" }} rollingUpdate: maxSurge: 1 maxUnavailable: 0 + {{- end }} template: metadata: labels: {{- include "coulomb.selectorLabels" . | nindent 8 }} @@ -59,16 +70,29 @@ spec: failureThreshold: {{ .Values.probes.liveness.failureThreshold }} {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} - {{- if .Values.persistence.media.enabled }} + {{- if or .Values.persistence.content.enabled .Values.persistence.media.enabled }} volumeMounts: + {{- if .Values.persistence.content.enabled }} + - name: content + mountPath: {{ .Values.persistence.content.mountPath | quote }} + {{- end }} + {{- if .Values.persistence.media.enabled }} - name: media mountPath: /app/media + {{- end }} {{- end }} - {{- if .Values.persistence.media.enabled }} + {{- if or .Values.persistence.content.enabled .Values.persistence.media.enabled }} volumes: + {{- if .Values.persistence.content.enabled }} + - name: content + persistentVolumeClaim: + claimName: {{ include "coulomb.fullname" . }}-content + {{- end }} + {{- if .Values.persistence.media.enabled }} - name: media persistentVolumeClaim: claimName: {{ include "coulomb.fullname" . }}-media + {{- end }} {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/coulomb-social/values.yaml b/charts/coulomb-social/values.yaml index 4272557..f8e3c20 100644 --- a/charts/coulomb-social/values.yaml +++ b/charts/coulomb-social/values.yaml @@ -1,10 +1,14 @@ image: + digest: "" repository: forgejo.coulomb.social/coulomb/coulomb-social tag: "" pullPolicy: IfNotPresent replicaCount: 1 +# Required single-writer content plane; avoid rolling surge/concurrent writes. +deploymentStrategy: Recreate + service: type: ClusterIP port: 80 @@ -54,6 +58,13 @@ probes: failureThreshold: 3 persistence: + content: + enabled: false + storageClass: local-path + size: 2Gi + mountPath: /app/var/content + # Keep PVC across release uninstall; backup/restore is a separate obligation. + retain: true media: enabled: false diff --git a/helm/coulomb-social-values.yaml b/helm/coulomb-social-values.yaml index c0d59fe..2cb0846 100644 --- a/helm/coulomb-social-values.yaml +++ b/helm/coulomb-social-values.yaml @@ -3,7 +3,8 @@ image: repository: forgejo.coulomb.social/coulomb/coulomb-social - tag: "7fcd0cf" + tag: "ac7bac1852c9" + digest: "sha256:bf975939e22b9b0a5d9ac05584ae6265ba201169df6dc48e506ec684b30a9c58" # Public host: app.coulomb.social (parallel to Bubble on apex coulomb.social). # Apex cutover is deferred until data + self-registration are ready. @@ -11,6 +12,7 @@ env: DJANGO_SETTINGS_MODULE: coulomb_social.settings.prod ALLOWED_HOSTS: app.coulomb.social,localhost CSRF_TRUSTED_ORIGINS: https://app.coulomb.social + CONTENT_ROOT: /app/var/content DEFAULT_TENANT_ID: "tenant:coulomb" OIDC_ENABLED: "true" OIDC_ISSUER: https://kc.coulomb.social @@ -25,3 +27,14 @@ env: probes: hostHeader: app.coulomb.social path: /healthz + +# One writable local-path volume; Recreate prevents concurrent old/new writers. +replicaCount: 1 +deploymentStrategy: Recreate +persistence: + content: + enabled: true + storageClass: local-path + size: 2Gi + mountPath: /app/var/content + retain: true diff --git a/workplans/RAPPS-WP-0015-coulomb-personal-content-release.md b/workplans/RAPPS-WP-0015-coulomb-personal-content-release.md new file mode 100644 index 0000000..69032ed --- /dev/null +++ b/workplans/RAPPS-WP-0015-coulomb-personal-content-release.md @@ -0,0 +1,96 @@ +--- +id: RAPPS-WP-0015 +type: workplan +title: "Coulomb-social persistent personal workspace release" +domain: financials +repo: railiance-apps +status: active +owner: codex +topic_slug: railiance-apps +created: "2026-10-06" +updated: "2026-10-06" +related: + - CSOC-WP-0008 +state_hub_workstream_id: "79abacf8-361e-51ab-829d-40dec8e142d1" +--- + +# RAPPS-WP-0015 — Personal Coulomb workspace + +Owning deployment change for CSOC-WP-0008. Prepare a reviewed upgrade from +7fcd0cf to the tested app with static assets and a 2Gi retained local-path +CONTENT_ROOT PVC. Use one replica and Recreate to prevent concurrent old/new +filesystem writers. Existing OIDC, PostgreSQL and secret references stay in use. + +## T01 — Prepare immutable source image + +```task +id: RAPPS-WP-0015-T01 +status: done +priority: high +state_hub_task_id: "e17488d4-60b8-58b7-a5d8-fbcdc55e1f0c" +``` + +Source repo runs make check; collect static assets in the container image; +exclude private local data from Docker context. Build/publish SHA-tagged image +with revision label and registry digest. Verify the image serves the real +clipboard asset through production settings without developer static hosting. + +## T02 — Validate persistent single-writer chart + +```task +id: RAPPS-WP-0015-T02 +status: done +priority: high +state_hub_task_id: "4e38dd8e-761b-58c8-8005-8490a49767ab" +``` + +Content PVC/mount/CONTENT_ROOT wiring, fsGroup 10001, keep policy and Recreate. +Reject multiple replicas, rolling writer strategy and root/mount mismatch. +Helm lint/render, server dry-run and diff; confirm resource/disk headroom and +that coulomb-social is not currently ArgoCD-managed. Publish the review branch. + +## T03 — Approved upgrade and recovery point + +```task +id: RAPPS-WP-0015-T03 +status: wait +priority: high +state_hub_task_id: "2ce05439-70dc-53cf-b704-5118687857cb" +``` + +Founder approves exact image digest, chart diff, 2Gi PVC and brief Recreate +interruption: ADMINISTER @ realm:kubernetes/railiance01, activation=APPROVED. +Capture a private pre-upgrade DB recovery point through the documented owner +backup path. Apply from railiance01 with Helm --atomic, preserve existing +tegwick identity and migration data. Do not run legacy env +secret-building scripts or dump live Secret objects. No apex cutover. + +## T04 — Personal sign-in and content acceptance + +```task +id: RAPPS-WP-0015-T04 +status: wait +priority: high +state_hub_task_id: "8bc4962a-d0d4-57cd-bf2b-295658b88765" +``` + +User signs in interactively as existing personal tegwick account. Confirm local +Member matches verified issuer/subject, grant only that member the nominated +workspace, and smoke page CRUD/duplicate/clipboard. Prove PVC write ownership, +restart durability and paired DB/content restore before real productive transfer. +CSOC-WP-0008 continues actual ReichelAg reconciliation and user review. + +## Prepared evidence — 2026-10-06 + +Source ac7bac1852c9, make check: 88 passed. Container production WhiteNoise +serves /static/spaces/copy-content.js HTTP 200, 2909 bytes, ClipboardItem present. +Published immutable registry digest: +sha256:bf975939e22b9b0a5d9ac05584ae6265ba201169df6dc48e506ec684b30a9c58. +Chart 0.2.0 pins that digest; lint and server dry-run pass; unsafe multiple +replicas, RollingUpdate and CONTENT_ROOT mismatch are refused. + +The user's actual login fails unknown client_id. Guarded read-only inspection +confirms the public coulomb-social client is absent. CSOC-WP-0008-T11 prepares +its restoration and shared KeyCape restart; no live change has been made. +Existing completed apps-pg daily backup is not a paired application restore. +T03/T04 remain waiting for exact live approval and personal acceptance.