From 73e2be20e1276ca63e4d469f346e68716dcfed4a Mon Sep 17 00:00:00 2001 From: spud <92900806+jamespud@users.noreply.github.com> Date: Wed, 23 Sep 2026 10:31:17 +0800 Subject: [PATCH] fix(helm): align provisioner state root for PVC mounts (#5625) * fix(helm): align provisioner state root for PVC mounts * test(helm): pin sandbox PVC subPath contract --- .github/workflows/chart.yaml | 8 ++ .../templates/provisioner-deployment.yaml | 5 + scripts/check_chart_sandbox_storage.sh | 98 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100755 scripts/check_chart_sandbox_storage.sh diff --git a/.github/workflows/chart.yaml b/.github/workflows/chart.yaml index fc77c4ef1..b975e5468 100644 --- a/.github/workflows/chart.yaml +++ b/.github/workflows/chart.yaml @@ -22,6 +22,7 @@ on: - ".github/workflows/chart.yaml" - "scripts/check_config_version.sh" - "scripts/check_chart_sandbox_service.sh" + - "scripts/check_chart_sandbox_storage.sh" - "scripts/check_chart_skill_upload_size.sh" jobs: @@ -50,6 +51,13 @@ jobs: - name: Validate sandbox Service-type gating run: bash scripts/check_chart_sandbox_service.sh + # Gateway-generated skill projections are sent to the provisioner as + # paths under DEER_FLOW_HOST_BASE_DIR. With the shared home PVC enabled, + # both components must use the same logical root so those paths can be + # translated into PVC subPaths instead of being rejected as out-of-base. + - name: Validate sandbox storage path contract + run: bash scripts/check_chart_sandbox_storage.sh + # Keep the rendered Ingress aligned with the Gateway's .skill upload # size, streaming, and long-running validation requirements. - name: Validate skill upload ingress policy diff --git a/deploy/helm/deer-flow/templates/provisioner-deployment.yaml b/deploy/helm/deer-flow/templates/provisioner-deployment.yaml index 6c26bbd23..23d27f96a 100644 --- a/deploy/helm/deer-flow/templates/provisioner-deployment.yaml +++ b/deploy/helm/deer-flow/templates/provisioner-deployment.yaml @@ -74,6 +74,11 @@ spec: {{- end }} # PVC mode — sandbox Pods mount the same PVCs the gateway uses. {{- if .Values.persistence.home.enabled }} + # Extra-mount paths originate in the Gateway's state namespace. + # Keep this logical base aligned so the provisioner can translate + # those paths into USERDATA PVC subPaths. + - name: DEER_FLOW_HOST_BASE_DIR + value: /app/backend/.deer-flow - name: USERDATA_PVC_NAME value: {{ include "deer-flow.homePVC" . | quote }} {{- end }} diff --git a/scripts/check_chart_sandbox_storage.sh b/scripts/check_chart_sandbox_storage.sh new file mode 100755 index 000000000..739e95608 --- /dev/null +++ b/scripts/check_chart_sandbox_storage.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# Assert the Helm chart keeps the Gateway and provisioner on the same logical +# DeerFlow state root when the shared home PVC backs sandbox projection mounts. +# +# The Gateway sends provisioner extra_mount host paths rooted at +# DEER_FLOW_HOST_BASE_DIR. In USERDATA_PVC mode the provisioner validates those +# paths against its own DEER_FLOW_HOST_BASE_DIR, then converts the relative +# suffix to a deer-flow/ PVC subPath. The Gateway must mount that same +# PVC subtree at the logical state root. Drift in any leg of this contract can +# either reject valid mounts with HTTP 400 or resolve them to the wrong subtree. +# +# When persistence.home.enabled=false the provisioner does not receive the +# Gateway's home PVC, so this check intentionally leaves its state-root env +# unset rather than making pod-local Gateway paths look hostPath-compatible. + +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CHART="$ROOT/deploy/helm/deer-flow" + +if ! command -v helm >/dev/null 2>&1; then + echo "::error::helm is required to run this check" >&2 + exit 1 +fi + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +render_component() { + local template="$1" + local output="$2" + shift 2 + helm template deer-flow "$CHART" --include-crds --show-only "$template" "$@" >"$output" +} + +if ! render_component templates/gateway-deployment.yaml "$TMP/gateway.yaml"; then + echo "::error::gateway chart render failed" >&2 + exit 1 +fi +if ! render_component templates/provisioner-deployment.yaml "$TMP/provisioner.yaml"; then + echo "::error::provisioner chart render failed" >&2 + exit 1 +fi +if ! render_component templates/provisioner-deployment.yaml "$TMP/provisioner-ephemeral.yaml" --set persistence.home.enabled=false; then + echo "::error::ephemeral provisioner chart render failed" >&2 + exit 1 +fi + +env_value() { + grep -A1 -E "^[[:space:]]*- name: $1$" "$2" | + grep -E "^[[:space:]]*value:" | + head -1 | + sed -E 's/^[[:space:]]*value:[[:space:]]*"?([^"]*)"?[[:space:]]*$/\1/' +} + +has_env() { grep -qE "^[[:space:]]*- name: $1$" "$2"; } + +has_gateway_home_mount_contract() { + grep -A1 -E '^[[:space:]]*mountPath: /app/backend/\.deer-flow$' "$1" | + grep -qE '^[[:space:]]*subPath: deer-flow$' +} + +errors=0 +check() { + if [ "$1" -eq 0 ]; then + echo " PASS $2" + else + echo " FAIL $2" + errors=$((errors + 1)) + fi +} + +gateway_root="$(env_value DEER_FLOW_HOST_BASE_DIR "$TMP/gateway.yaml")" +provisioner_root="$(env_value DEER_FLOW_HOST_BASE_DIR "$TMP/provisioner.yaml")" + +echo "## Shared home PVC render" +[ -n "$gateway_root" ]; check $? "Gateway DEER_FLOW_HOST_BASE_DIR is present" +[ -n "$provisioner_root" ]; check $? "Provisioner DEER_FLOW_HOST_BASE_DIR is present" +[ "$gateway_root" = "$provisioner_root" ]; check $? "Gateway and provisioner state roots match" +[ "$gateway_root" = "/app/backend/.deer-flow" ]; check $? "Shared logical state root is /app/backend/.deer-flow" +has_env USERDATA_PVC_NAME "$TMP/provisioner.yaml"; check $? "Provisioner USERDATA_PVC_NAME is present" +has_gateway_home_mount_contract "$TMP/gateway.yaml"; check $? "Gateway home mount keeps subPath deer-flow at the shared logical root" + +echo "## persistence.home.enabled=false render" +if has_env DEER_FLOW_HOST_BASE_DIR "$TMP/provisioner-ephemeral.yaml"; then + check 1 "Provisioner state root stays unset without the shared home PVC" +else + check 0 "Provisioner state root stays unset without the shared home PVC" +fi + +echo +if [ "$errors" -eq 0 ]; then + echo "All sandbox storage path-contract assertions passed." + exit 0 +fi + +echo "::error::$errors assertion(s) failed (see above)" >&2 +exit 1