From b859530fcf1e5d2418f2f291afc8a462602fd15b Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 15 Jun 2026 15:40:57 +0200 Subject: [PATCH] Add reuse service landing page --- charts/reuse-surface/templates/_helpers.tpl | 23 ++- .../reuse-surface/templates/deployment.yaml | 10 +- charts/reuse-surface/templates/ingress.yaml | 21 ++- .../templates/landing-configmap.yaml | 135 ++++++++++++++ .../templates/landing-deployment.yaml | 59 ++++++ .../templates/landing-service.yaml | 18 ++ .../templates/redirect-ingress.yaml | 23 +++ .../templates/redirect-middleware.yaml | 11 ++ charts/reuse-surface/templates/service.yaml | 6 +- charts/reuse-surface/values.yaml | 39 +++- docs/reuse-surface-on-railiance01.md | 27 ++- docs/s5-app-onboarding-checklist.md | 39 +++- helm/reuse-surface-values.yaml | 21 ++- ...RAILIANCE-WP-0008-service-landing-pages.md | 169 ++++++++++++++++++ 14 files changed, 589 insertions(+), 12 deletions(-) create mode 100644 charts/reuse-surface/templates/landing-configmap.yaml create mode 100644 charts/reuse-surface/templates/landing-deployment.yaml create mode 100644 charts/reuse-surface/templates/landing-service.yaml create mode 100644 charts/reuse-surface/templates/redirect-ingress.yaml create mode 100644 charts/reuse-surface/templates/redirect-middleware.yaml create mode 100644 workplans/RAILIANCE-WP-0008-service-landing-pages.md diff --git a/charts/reuse-surface/templates/_helpers.tpl b/charts/reuse-surface/templates/_helpers.tpl index d468ad0..1c46bdb 100644 --- a/charts/reuse-surface/templates/_helpers.tpl +++ b/charts/reuse-surface/templates/_helpers.tpl @@ -17,9 +17,30 @@ app.kubernetes.io/name: {{ include "reuse.fullname" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} +{{- define "reuse.landingFullname" -}} +{{- printf "%s-landing" (include "reuse.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "reuse.redirectMiddlewareName" -}} +{{- printf "%s-redirect-https" (include "reuse.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "reuse.landingSelectorLabels" -}} +app.kubernetes.io/name: {{ include "reuse.fullname" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: landing +{{- end -}} + {{- define "reuse.image" -}} {{- if not .Values.image.tag -}} {{- fail "image.tag is required - pin it in helm/reuse-surface-values.yaml" -}} {{- end -}} {{- printf "%s:%s" .Values.image.repository .Values.image.tag -}} -{{- end -}} \ No newline at end of file +{{- end -}} + +{{- define "reuse.landingImage" -}} +{{- if not .Values.landing.image.tag -}} +{{- fail "landing.image.tag is required when landing.enabled=true" -}} +{{- end -}} +{{- printf "%s:%s" .Values.landing.image.repository .Values.landing.image.tag -}} +{{- end -}} diff --git a/charts/reuse-surface/templates/deployment.yaml b/charts/reuse-surface/templates/deployment.yaml index 3fd100e..b1c47e7 100644 --- a/charts/reuse-surface/templates/deployment.yaml +++ b/charts/reuse-surface/templates/deployment.yaml @@ -2,7 +2,9 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "reuse.fullname" . }} - labels: {{- include "reuse.labels" . | nindent 4 }} + labels: + {{- include "reuse.labels" . | nindent 4 }} + app.kubernetes.io/component: api spec: replicas: {{ .Values.replicaCount }} selector: @@ -14,7 +16,9 @@ spec: maxUnavailable: 0 template: metadata: - labels: {{- include "reuse.selectorLabels" . | nindent 8 }} + labels: + {{- include "reuse.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: api spec: enableServiceLinks: false securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} @@ -78,4 +82,4 @@ spec: {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} \ No newline at end of file + {{- end }} diff --git a/charts/reuse-surface/templates/ingress.yaml b/charts/reuse-surface/templates/ingress.yaml index 25d684b..12e2d33 100644 --- a/charts/reuse-surface/templates/ingress.yaml +++ b/charts/reuse-surface/templates/ingress.yaml @@ -18,6 +18,24 @@ spec: - host: {{ .Values.ingress.host }} http: paths: + {{- if .Values.landing.enabled }} + {{- range .Values.ingress.apiPaths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "reuse.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + - path: / + pathType: Prefix + backend: + service: + name: {{ include "reuse.landingFullname" . }} + port: + number: {{ .Values.landing.service.port }} + {{- else }} - path: / pathType: Prefix backend: @@ -25,4 +43,5 @@ spec: name: {{ include "reuse.fullname" . }} port: number: {{ .Values.service.port }} -{{- end }} \ No newline at end of file + {{- end }} +{{- end }} diff --git a/charts/reuse-surface/templates/landing-configmap.yaml b/charts/reuse-surface/templates/landing-configmap.yaml new file mode 100644 index 0000000..84a7298 --- /dev/null +++ b/charts/reuse-surface/templates/landing-configmap.yaml @@ -0,0 +1,135 @@ +{{- if .Values.landing.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "reuse.landingFullname" . }} + labels: + {{- include "reuse.labels" . | nindent 4 }} + app.kubernetes.io/component: landing +data: + index.html: | +{{ if .Values.landing.html }} +{{ tpl .Values.landing.html . | nindent 4 }} +{{ else }} + + + + + + {{- if .Values.landing.noindex }} + + {{- end }} + {{- if and .Values.landing.redirect.enabled .Values.landing.redirect.target }} + + {{- end }} + {{ .Values.landing.title }} + + + +
+

{{ .Values.landing.eyebrow }}

+

{{ .Values.landing.title }}

+

{{ .Values.landing.body }}

+ {{- $target := .Values.landing.primaryUrl }} + {{- if and .Values.landing.redirect.enabled .Values.landing.redirect.target }} + {{- $target = .Values.landing.redirect.target }} + {{- end }} + {{- if $target }} + + {{- end }} + {{- if .Values.landing.links }} + + {{- end }} +
+ + +{{ end }} +{{- end }} diff --git a/charts/reuse-surface/templates/landing-deployment.yaml b/charts/reuse-surface/templates/landing-deployment.yaml new file mode 100644 index 0000000..a8bdb6f --- /dev/null +++ b/charts/reuse-surface/templates/landing-deployment.yaml @@ -0,0 +1,59 @@ +{{- if .Values.landing.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "reuse.landingFullname" . }} + labels: + {{- include "reuse.labels" . | nindent 4 }} + app.kubernetes.io/component: landing +spec: + replicas: 1 + selector: + matchLabels: + {{- include "reuse.landingSelectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + template: + metadata: + labels: + {{- include "reuse.landingSelectorLabels" . | nindent 8 }} + spec: + enableServiceLinks: false + containers: + - name: landing + image: {{ include "reuse.landingImage" . | quote }} + imagePullPolicy: {{ .Values.landing.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.landing.service.targetPort }} + protocol: TCP + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 3 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 30 + timeoutSeconds: 3 + failureThreshold: 3 + resources: {{- toYaml .Values.landing.resources | nindent 12 }} + volumeMounts: + - name: landing-page + mountPath: /usr/share/nginx/html/index.html + subPath: index.html + readOnly: true + volumes: + - name: landing-page + configMap: + name: {{ include "reuse.landingFullname" . }} +{{- end }} diff --git a/charts/reuse-surface/templates/landing-service.yaml b/charts/reuse-surface/templates/landing-service.yaml new file mode 100644 index 0000000..927e331 --- /dev/null +++ b/charts/reuse-surface/templates/landing-service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.landing.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "reuse.landingFullname" . }} + labels: + {{- include "reuse.labels" . | nindent 4 }} + app.kubernetes.io/component: landing +spec: + type: ClusterIP + selector: + {{- include "reuse.landingSelectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.landing.service.port }} + targetPort: http + protocol: TCP +{{- end }} diff --git a/charts/reuse-surface/templates/redirect-ingress.yaml b/charts/reuse-surface/templates/redirect-ingress.yaml new file mode 100644 index 0000000..9aa05ed --- /dev/null +++ b/charts/reuse-surface/templates/redirect-ingress.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.ingress.enabled .Values.ingress.redirectHttp.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "reuse.fullname" . }}-http-redirect + labels: {{- include "reuse.labels" . | nindent 4 }} + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/router.middlewares: {{ printf "%s-%s@kubernetescrd" .Release.Namespace (include "reuse.redirectMiddlewareName" .) | quote }} +spec: + ingressClassName: {{ .Values.ingress.className }} + rules: + - host: {{ .Values.ingress.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "reuse.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/charts/reuse-surface/templates/redirect-middleware.yaml b/charts/reuse-surface/templates/redirect-middleware.yaml new file mode 100644 index 0000000..42bd2a7 --- /dev/null +++ b/charts/reuse-surface/templates/redirect-middleware.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.ingress.enabled .Values.ingress.redirectHttp.enabled }} +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ include "reuse.redirectMiddlewareName" . }} + labels: {{- include "reuse.labels" . | nindent 4 }} +spec: + redirectScheme: + scheme: https + permanent: {{ .Values.ingress.redirectHttp.permanent }} +{{- end }} diff --git a/charts/reuse-surface/templates/service.yaml b/charts/reuse-surface/templates/service.yaml index 6ad53b9..906e3fb 100644 --- a/charts/reuse-surface/templates/service.yaml +++ b/charts/reuse-surface/templates/service.yaml @@ -5,9 +5,11 @@ metadata: labels: {{- include "reuse.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} - selector: {{- include "reuse.selectorLabels" . | nindent 4 }} + selector: + {{- include "reuse.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: api ports: - name: http port: {{ .Values.service.port }} targetPort: http - protocol: TCP \ No newline at end of file + protocol: TCP diff --git a/charts/reuse-surface/values.yaml b/charts/reuse-surface/values.yaml index de526c7..55ceb1d 100644 --- a/charts/reuse-surface/values.yaml +++ b/charts/reuse-surface/values.yaml @@ -26,11 +26,48 @@ resources: envSecretName: reuse-surface-env +landing: + enabled: false + image: + repository: nginxinc/nginx-unprivileged + tag: "1.27-alpine" + pullPolicy: IfNotPresent + service: + port: 8080 + targetPort: 8080 + noindex: true + title: "Railiance service endpoint" + eyebrow: "Railiance S5" + body: "This endpoint is available for automated clients and operators." + buttonLabel: "Continue" + primaryUrl: "" + redirect: + enabled: false + target: "" + delaySeconds: 5 + links: [] + html: "" + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 50m + memory: 64Mi + ingress: enabled: true className: traefik host: reuse.coulomb.social tls: true + redirectHttp: + enabled: false + permanent: true + apiPaths: + - path: /health + pathType: Exact + - path: /v1 + pathType: Prefix annotations: traefik.ingress.kubernetes.io/router.entrypoints: websecure traefik.ingress.kubernetes.io/router.tls: "true" @@ -56,4 +93,4 @@ securityContext: {} nodeSelector: {} tolerations: [] -affinity: {} \ No newline at end of file +affinity: {} diff --git a/docs/reuse-surface-on-railiance01.md b/docs/reuse-surface-on-railiance01.md index 1516575..a0b6d6c 100644 --- a/docs/reuse-surface-on-railiance01.md +++ b/docs/reuse-surface-on-railiance01.md @@ -42,8 +42,30 @@ export REUSE_SURFACE_URL=http://127.0.0.1:18001 | Chart | `charts/reuse-surface` | | Values | `helm/reuse-surface-values.yaml` | | Image | `gitea.coulomb.social/coulomb/reuse-surface:` | +| Landing image | `nginxinc/nginx-unprivileged:1.27-alpine` | | Secret | `reuse-surface-env` (`REUSE_SURFACE_TOKEN`) | +## Browser landing page + +`https://reuse.coulomb.social/` serves a static no-login landing page from the +Helm-managed `reuse-surface-landing` Deployment and Service. It exists for +humans who open the hostname in a browser; it does not change the API service. + +Ingress routing is intentionally split: + +- HTTP `/` redirects permanently to `https://reuse.coulomb.social/`; +- HTTPS `/health` and `/v1/*` route to `svc/reuse-surface`; +- HTTPS `/` and other non-API browser paths route to + `svc/reuse-surface-landing`. + +The rendered page includes `noindex,nofollow`, a short service description, and +links to `/health`, `/v1/federated`, and this operator runbook. It must not +include `REUSE_SURFACE_TOKEN` or any other runtime secret. + +Rollback: set `landing.enabled: false` in `helm/reuse-surface-values.yaml` and +run `KUBECONFIG=~/.kube/config-hosteurope make reuse-deploy`; the ingress will +return to routing all `/` traffic to the API service. + ## Deploy ```bash @@ -67,7 +89,10 @@ KUBECONFIG=~/.kube/config-hosteurope make reuse-status ## Smoke checks ```bash +curl -I http://reuse.coulomb.social/ +curl -k --resolve reuse.coulomb.social:443:92.205.62.239 https://reuse.coulomb.social/ curl -k --resolve reuse.coulomb.social:443:92.205.62.239 https://reuse.coulomb.social/health +curl -k --resolve reuse.coulomb.social:443:92.205.62.239 https://reuse.coulomb.social/v1/federated export REUSE_SURFACE_TOKEN=$(KUBECONFIG=~/.kube/config-hosteurope kubectl get secret reuse-surface-env -n reuse \ -o jsonpath='{.data.REUSE_SURFACE_TOKEN}' | base64 -d) @@ -95,4 +120,4 @@ KUBECONFIG=~/.kube/config-hosteurope make reuse-deploy ``` Bootstrap copy on CoulombCore (`92.205.130.254`) was removed 2026-06-15 — use -`config-hosteurope` only. \ No newline at end of file +`config-hosteurope` only. diff --git a/docs/s5-app-onboarding-checklist.md b/docs/s5-app-onboarding-checklist.md index 642bb96..eb054e5 100644 --- a/docs/s5-app-onboarding-checklist.md +++ b/docs/s5-app-onboarding-checklist.md @@ -64,6 +64,40 @@ workplans first. headers to a value included in the app's allowed hosts. - [ ] Keep readiness and liveness paths stable and unauthenticated. +## Endpoint Landing Pages + +- [ ] Give every public S5 endpoint an intentional browser response at `/`. +- [ ] For API-only services, serve a static informational landing page at `/` + that states the service purpose, avoids any login claim, does not expose or + hint at runtime secrets, and links only to non-secret health/status or + operator documentation. +- [ ] For UI-backed services, a landing page may forward users to the canonical + login or application route, but it must also include a visible button for the + same destination. +- [ ] Preserve existing machine-facing paths. Health probes, API prefixes, + OAuth callbacks, and static asset routes must continue to reach their owning + backend after the landing page is enabled. +- [ ] For API-only endpoints, route explicit API/probe paths such as `/health` + and `/v1` to the API service, then use `/` as the landing fallback. Avoid + competing exact `/` and prefix `/` rules for different backends. +- [ ] Add `noindex` metadata for operator and service landing pages that are + not intended as public marketing pages. + +Example UI-backed landing values: + +```yaml +landing: + enabled: true + title: "Application sign-in" + eyebrow: "app.example.coulomb.social" + body: "You are being sent to the application sign-in page." + buttonLabel: "Continue to sign-in" + redirect: + enabled: true + target: "/login/" + delaySeconds: 5 +``` + ## Validation And Smoke Tests - [ ] Run `make check-tools`. @@ -73,8 +107,8 @@ workplans first. - [ ] Use the persistent-pod plus `kubectl exec` smoke pattern from `docs/operator-recipes.md`. - [ ] Capture app-level deployment evidence: dry-run result, rollout status, - HTTPS or service smoke check, migration result when applicable, and rollback - note. + HTTPS or service smoke check, landing-page check when enabled, migration + result when applicable, and rollback note. ## Runbook Baseline @@ -86,6 +120,7 @@ Each S5 app runbook should include: - day-to-day operator commands; - image promotion steps; - rollback behavior and migration warning; +- public `/` landing-page behavior and the canonical login or API entrypoints; - troubleshooting for probes, database URLs, TLS, and app-specific failure modes; - backup and restore readiness gate; diff --git a/helm/reuse-surface-values.yaml b/helm/reuse-surface-values.yaml index d89df59..af38bc2 100644 --- a/helm/reuse-surface-values.yaml +++ b/helm/reuse-surface-values.yaml @@ -2,4 +2,23 @@ # REUSE_SURFACE_TOKEN is supplied via Secret reuse-surface-env. image: - tag: "cb7a6e4" \ No newline at end of file + tag: "cb7a6e4" + +landing: + enabled: true + title: "REUSE federation endpoint" + eyebrow: "reuse.coulomb.social" + body: "This is the Railiance REUSE capability federation service. It is an API endpoint for automated clients and operators; there is no browser login for this service." + buttonLabel: "Open federated index" + primaryUrl: "/v1/federated" + links: + - label: "Health check" + url: "/health" + - label: "Federated capability index" + url: "/v1/federated" + - label: "Operator runbook" + url: "https://gitea.coulomb.social/coulomb/railiance-apps/src/branch/main/docs/reuse-surface-on-railiance01.md" + +ingress: + redirectHttp: + enabled: true diff --git a/workplans/RAILIANCE-WP-0008-service-landing-pages.md b/workplans/RAILIANCE-WP-0008-service-landing-pages.md new file mode 100644 index 0000000..78a7695 --- /dev/null +++ b/workplans/RAILIANCE-WP-0008-service-landing-pages.md @@ -0,0 +1,169 @@ +--- +id: RAILIANCE-WP-0008 +type: workplan +title: "Add friendly landing pages for S5 service endpoints" +domain: railiance +repo: railiance-apps +status: finished +owner: codex +topic_slug: railiance +created: "2026-06-15" +updated: "2026-06-15" +state_hub_workstream_id: "41b9deea-a935-4372-8916-b3981336f597" +--- + +# Add friendly landing pages for S5 service endpoints + +Create a small, reusable landing-page pattern for public S5 application +endpoints. The page should give humans a clear next step without changing the +machine-facing API contract: + +- UI-backed applications may show a friendly page with a short redirect notice + and a visible **Continue** button to the correct login or application route. +- API-only services should show a proper no-login informational page at `/`, + with links to health/status/runbook documentation where appropriate. + +First rollout target: **`https://reuse.coulomb.social`**, which hosts the +`reuse-surface` federation API and intentionally has no browser login UI. + +## Current Context + +`RAILIANCE-WP-0007` deployed the `reuse-surface` Helm release and runbook for +`reuse.coulomb.social`. The release is useful to machines and operators, but a +human visiting `/` should not be left with an API-shaped response or a dead end. + +The implementation must preserve the existing CLI/API surface: + +- `/health` remains suitable for Kubernetes probes and external smoke checks. +- `/v1/*` API routes continue to reach the `reuse-surface` service. +- Authenticated federation operations continue to use `REUSE_SURFACE_TOKEN` and + must not expose token material in the landing page, chart values, or logs. + +## Design Constraints + +- Keep the page static and non-secret. +- Prefer explicit Ingress path routing over changing API semantics when a + service has no upstream web UI. +- Use exact `/` routing for the landing page when possible, so API prefixes are + unaffected. +- Add `noindex` metadata for operator/service landing pages that are not meant + to be public marketing surfaces. +- Keep the pattern reusable for later services with login destinations. + +--- + +## Define Landing Page Contract + +```task +id: RAILIANCE-WP-0008-T01 +status: done +priority: high +state_hub_task_id: "ca47efdb-ed0e-47bf-90f3-5a78c6861ebf" +``` + +Document the endpoint contract in `docs/s5-app-onboarding-checklist.md` or a +small companion doc: + +- API-only service: static informational page at `/`, no login claim, no token + hints, links to health/status and operator runbook. +- UI-backed service: landing page may auto-forward after a short delay and must + include a visible button to the canonical login/application route. +- All variants must preserve health, API, OAuth callback, and asset paths. + +Done when future S5 app workplans can cite a single landing-page rule instead +of rediscovering the behavior per service. + +## Add Reusable Helm Support + +```task +id: RAILIANCE-WP-0008-T02 +status: done +priority: high +state_hub_task_id: "44dfe49f-a3f4-4381-a4ca-eb9218099868" +``` + +Add a reusable Helm pattern for a static landing page, either as shared snippets +or as chart-local templates following one documented convention. The pattern +should support: + +- enable/disable flag in values; +- static HTML content from a ConfigMap; +- a lightweight static HTTP container or equivalent service; +- exact `/` Ingress path to the landing service; +- prefix routes for API and app paths to the existing backend; +- optional redirect target and button label for UI-backed applications. + +Done when `helm template` can render both disabled and enabled landing-page +variants without changing non-landing deployments. + +## Implement API-Only Landing Page For reuse.coulomb.social + +```task +id: RAILIANCE-WP-0008-T03 +status: done +priority: high +state_hub_task_id: "4e90315d-f0f0-49ca-b44f-533c23a44e96" +``` + +Enable the landing-page pattern in `charts/reuse-surface` and +`helm/reuse-surface-values.yaml` for `reuse.coulomb.social`. + +Content should make clear that this endpoint is the Railiance REUSE capability +federation service, not a login application. Include non-secret operator links +or text for: + +- service purpose; +- `/health`; +- `/v1/federated`; +- `docs/reuse-surface-on-railiance01.md`; +- noindex metadata. + +Done when a browser request to `/` receives the landing page while `/health` +and `/v1/federated` still reach the API service. + +## Add Login-Forward Variant For UI Applications + +```task +id: RAILIANCE-WP-0008-T04 +status: done +priority: medium +state_hub_task_id: "80c843b2-68d0-4f8a-aba6-39db1a2a6f70" +``` + +Prepare the values contract and at least one documented example for services +that do have a browser UI. The page should explain that the user is being sent +to the application and include a visible button to the configured login or +application route. + +Done when a future UI-backed app can set only values such as `redirectTarget`, +`buttonLabel`, and explanatory copy, without editing templates. + +## Verify, Deploy, And Update Runbook + +```task +id: RAILIANCE-WP-0008-T05 +status: done +priority: medium +state_hub_task_id: "0fc250a8-8fea-4c88-bd81-ad7ecf6143a1" +``` + +Render, deploy, and smoke-test the reuse landing page on railiance01: + +- `make reuse-dry-run`; +- deploy with pinned image and non-secret landing values; +- verify `/`, `/health`, and `/v1/federated` over the public hostname; +- confirm no secret values appear in manifests or rendered HTML; +- update `docs/reuse-surface-on-railiance01.md` with the landing-page behavior + and rollback notes. + +Done when the public browser experience is friendly and the existing +machine-facing API checks still pass. + +Completed 2026-06-15: deployed Helm revision 5 to namespace `reuse`. +`reuse-surface-landing` is Running, HTTP `/` redirects to HTTPS, HTTPS `/` +returns `200 text/html`, `/health` returns `200 application/json`, +`/v1/federated` returns `200 application/json` with 12 capabilities, and the +fetched landing page contains no token references. Follow-up fix separated the +API and landing Service selectors with `app.kubernetes.io/component` labels and +changed ingress routing to explicit API paths (`/health`, `/v1`) plus landing +fallback `/`.