#!/usr/bin/env sh set -eu SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PLATFORM_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd) OPS_GATEWAY_REPO="${OPS_GATEWAY_REPO:-$PLATFORM_ROOT/../../data/NODEDC_TASKMANAGER_CODEXAPI}" ENGINE_REPO="${ENGINE_REPO:-$PLATFORM_ROOT/../NODEDC_ENGINE_INFRA}" PLATFORM_ENV="${PLATFORM_ENV:-$PLATFORM_ROOT/infra/.env}" passed=0 failed=0 warnings=0 section() { printf '\n== %s ==\n' "$1" } pass() { passed=$((passed + 1)) printf 'PASS %s\n' "$1" } fail() { failed=$((failed + 1)) printf 'FAIL %s\n' "$1" >&2 } warn() { warnings=$((warnings + 1)) printf 'WARN %s\n' "$1" >&2 } require_file() { file="$1" label="$2" if [ -f "$file" ]; then pass "$label" else fail "$label (missing file: $file)" fi } require_contains() { file="$1" needle="$2" label="$3" if [ ! -f "$file" ]; then fail "$label (missing file: $file)" return 0 fi if grep -Fq "$needle" "$file"; then pass "$label" else fail "$label (missing: $needle)" fi } read_env() { file="$1" key="$2" if [ ! -f "$file" ]; then return 0 fi awk -F= -v key="$key" ' $0 ~ "^[[:space:]]*#" { next } $1 == key { value = substr($0, index($0, "=") + 1) gsub(/^[[:space:]]+|[[:space:]]+$/, "", value) gsub(/^"|"$/, "", value) print value exit } ' "$file" } require_env_value() { file="$1" key="$2" expected="$3" label="$4" actual=$(read_env "$file" "$key" || true) if [ "$actual" = "$expected" ]; then pass "$label" else fail "$label (expected $key=$expected, got ${actual:-})" fi } section "Required files" require_file "$PLATFORM_ROOT/infra/.env.example" "Platform local env example exists" require_file "$PLATFORM_ROOT/infra/synology/.env.synology.example" "Platform Synology env example exists" require_file "$PLATFORM_ROOT/infra/docker-compose.dev.yml" "Platform local compose exists" require_file "$PLATFORM_ROOT/infra/synology/docker-compose.platform-http.yml" "Platform Synology compose exists" require_file "$OPS_GATEWAY_REPO/.env.example" "Ops Gateway local env example exists" require_file "$OPS_GATEWAY_REPO/.env.synology.example" "Ops Gateway Synology env example exists" require_file "$OPS_GATEWAY_REPO/docker-compose.local.yml" "Ops Gateway local compose exists" require_file "$OPS_GATEWAY_REPO/docker-compose.synology.yml" "Ops Gateway Synology compose exists" require_file "$ENGINE_REPO/docker-compose.yml" "Engine compose exists" section "Platform local contract" require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_OPS_ENTITLEMENT_URL" "http://host.docker.internal:4100/api/internal/v1/ai-workspace/entitlements" "Local Platform example points Ops entitlement to local Gateway" require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_HUB_PUBLIC_URL" "wss://ai-hub.nodedc.ru/api/ai-workspace/hub" "Local Platform example keeps deployed Hub as hybrid worker relay by default" require_env_value "$PLATFORM_ROOT/infra/.env.example" "AI_WORKSPACE_HUB_INTERNAL_URL" "https://ai-hub.nodedc.ru" "Local Platform example has explicit Hub internal URL" require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_OPS_ENTITLEMENT_URL=http://host.docker.internal:4100/api/internal/v1/ai-workspace/entitlements" "Local env generator emits local Ops entitlement URL" require_contains "$PLATFORM_ROOT/infra/scripts/init-dev-env.sh" "AI_WORKSPACE_HUB_INTERNAL_URL=https://ai-hub.nodedc.ru" "Local env generator emits explicit Hub internal URL" section "Platform Synology contract" require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "NODEDC_AI_WORKSPACE_ASSISTANT_URL" "http://ai-workspace-assistant:18082" "Synology Platform exposes Assistant to app services on compose network" require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_OPS_ENTITLEMENT_URL" "http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements" "Synology Platform points Ops entitlement to Synology Gateway" require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_HUB_PUBLIC_URL" "wss://ai-hub.nodedc.ru/api/ai-workspace/hub" "Synology Platform worker-facing Hub URL is public relay" require_env_value "$PLATFORM_ROOT/infra/synology/.env.synology.example" "AI_WORKSPACE_HUB_INTERNAL_URL" "https://ai-hub.nodedc.ru" "Synology Platform backend Hub URL is explicit" require_contains "$PLATFORM_ROOT/infra/synology/verify-current-runtime.sh" 'AI_WORKSPACE_OPS_ENTITLEMENT_URL" = "http://172.22.0.222:18190/api/internal/v1/ai-workspace/entitlements"' "Synology verify checks Ops entitlement URL" section "Ops Gateway contract" require_env_value "$OPS_GATEWAY_REPO/.env.example" "NODEDC_AGENT_GATEWAY_PUBLIC_URL" "https://ops-agents.nodedc.ru" "Ops Gateway local example keeps worker-facing MCP URL explicit" require_env_value "$OPS_GATEWAY_REPO/.env.example" "NODEDC_TASKER_INTERNAL_URL" "http://task.local.nodedc" "Ops Gateway local example points downstream to local Tasker" require_env_value "$OPS_GATEWAY_REPO/.env.synology.example" "NODEDC_AGENT_GATEWAY_PUBLIC_URL" "https://ops-agents.nodedc.ru" "Ops Gateway Synology public MCP URL is public relay" require_env_value "$OPS_GATEWAY_REPO/.env.synology.example" "NODEDC_TASKER_INTERNAL_URL" "http://172.22.0.222:18090" "Ops Gateway Synology downstream points to Synology Tasker" require_contains "$OPS_GATEWAY_REPO/docker-compose.synology.yml" '${HOST_BIND:-172.22.0.222}:${HOST_PORT:-18190}:${PORT:-4100}' "Ops Gateway Synology compose binds expected internal port" require_contains "$OPS_GATEWAY_REPO/README.md" "The response uses the AI Workspace adapter contract" "Ops Gateway README documents AI Workspace adapter contract" section "Engine contract" require_contains "$ENGINE_REPO/docker-compose.yml" 'NODEDC_AI_WORKSPACE_ASSISTANT_URL: ${NODEDC_AI_WORKSPACE_ASSISTANT_URL:-http://ai-workspace-assistant:18082}' "Engine compose accepts configurable Assistant URL" require_contains "$ENGINE_REPO/docker-compose.yml" 'NDC_AI_WORKSPACE_HUB_URL: ${NDC_AI_WORKSPACE_HUB_URL:-wss://ai-hub.nodedc.ru/api/ai-workspace/hub}' "Engine compose accepts configurable worker-facing Hub URL" require_contains "$ENGINE_REPO/docker-compose.yml" 'NDC_AI_WORKSPACE_HUB_HTTP_URL: ${NDC_AI_WORKSPACE_HUB_HTTP_URL:-https://ai-hub.nodedc.ru}' "Engine compose accepts configurable Hub HTTP URL" require_contains "$ENGINE_REPO/nodedc-source/server/aiWorkspace/assistantRegistryClient.js" "'X-NODEDC-User-Role': role" "Engine forwards user role to Assistant" require_contains "$ENGINE_REPO/nodedc-source/server/aiWorkspace/assistantRegistryClient.js" "'X-NODEDC-User-Groups': groups.join(',')" "Engine forwards user groups to Assistant" section "Current local env warnings" if [ -f "$PLATFORM_ENV" ]; then local_ops_entitlement=$(read_env "$PLATFORM_ENV" AI_WORKSPACE_OPS_ENTITLEMENT_URL || true) if [ -z "$local_ops_entitlement" ]; then warn "Current Platform env has no AI_WORKSPACE_OPS_ENTITLEMENT_URL; current Mac remains hybrid/contract-only until env is deliberately updated." else pass "Current Platform env has AI_WORKSPACE_OPS_ENTITLEMENT_URL" fi else warn "Current Platform env not found: $PLATFORM_ENV" fi section "Summary" printf 'passed=%s failed=%s warnings=%s\n' "$passed" "$failed" "$warnings" if [ "$failed" -ne 0 ]; then exit 1 fi