feat: move distribution builds to platform Docker builder
This commit is contained in:
+388
-64
@@ -8,14 +8,35 @@ source "$ROOT_DIR/scripts/local-debug/env.sh"
|
||||
PLATFORM_URL="$(local_debug_platform_url)"
|
||||
API_URL="$PLATFORM_URL/api/v1"
|
||||
WORK_DIR="$LOCAL_DEBUG_ROOT/smoke"
|
||||
SMOKE_INVOCATION_ID="${SMOKE_INVOCATION_ID:-$(date -u +%Y%m%d%H%M%S)-$$}"
|
||||
SERVER_LOCAL_ID="server-local-debug-$SMOKE_INVOCATION_ID"
|
||||
SCUM_ALPHA_ID="scum-alpha-$SMOKE_INVOCATION_ID"
|
||||
SCUM_BETA_ID="scum-beta-$SMOKE_INVOCATION_ID"
|
||||
SCUM_DYNAMIC_ID="scum-dynamic-$SMOKE_INVOCATION_ID"
|
||||
GENERATED_RUN_ENDPOINT_ID="server-run-$SERVER_LOCAL_ID"
|
||||
GENERATED_RUN_BIN="$WORK_DIR/generated-$SERVER_LOCAL_ID-run"
|
||||
GENERATED_RUN_LOG="$LOCAL_DEBUG_LOG_DIR/generated-$SERVER_LOCAL_ID-run.log"
|
||||
GENERATED_RUN_PID_FILE="$LOCAL_DEBUG_PID_DIR/generated-$SERVER_LOCAL_ID-run.pid"
|
||||
mkdir -p "$WORK_DIR"
|
||||
|
||||
cat >"$WORK_DIR/run-build-config.env" <<EOF
|
||||
SMOKE_INVOCATION_ID=$SMOKE_INVOCATION_ID
|
||||
SERVER_LOCAL_ID=$SERVER_LOCAL_ID
|
||||
SCUM_ALPHA_ID=$SCUM_ALPHA_ID
|
||||
SCUM_BETA_ID=$SCUM_BETA_ID
|
||||
SCUM_DYNAMIC_ID=$SCUM_DYNAMIC_ID
|
||||
GENERATED_RUN_ENDPOINT_ID=$GENERATED_RUN_ENDPOINT_ID
|
||||
PLATFORM_RUN_RELEASE_URL=$PLATFORM_RUN_RELEASE_URL
|
||||
RUN_SOURCE_DIR=$RUN_SOURCE_DIR
|
||||
RUN_REPO_DIR=$RUN_REPO_DIR
|
||||
RUN_BUILD_BUCKET_ROOT=$RUN_BUILD_BUCKET_ROOT
|
||||
RUN_BUILD_SOURCE_ROOT=$RUN_BUILD_SOURCE_ROOT
|
||||
RUN_BOOTSTRAP_BIN=$RUN_BOOTSTRAP_BIN
|
||||
PLATFORM_BUILDER_DOCKER_BINARY=$PLATFORM_BUILDER_DOCKER_BINARY
|
||||
PLATFORM_BUILDER_IMAGE=$PLATFORM_BUILDER_IMAGE
|
||||
PLATFORM_BUILDER_SOURCE_DIR=$PLATFORM_BUILDER_SOURCE_DIR
|
||||
PLATFORM_BUILDER_WORKSPACE_DIR=$PLATFORM_BUILDER_WORKSPACE_DIR
|
||||
PLATFORM_BUILDER_TIMEOUT_SECONDS=$PLATFORM_BUILDER_TIMEOUT_SECONDS
|
||||
RUN_WORKSPACE_ROOT=$RUN_WORKSPACE_ROOT
|
||||
RUN_SPOOL_ROOT=$RUN_SPOOL_ROOT
|
||||
RUN_MAX_JOBS=$RUN_MAX_JOBS
|
||||
@@ -23,10 +44,10 @@ EOF
|
||||
|
||||
prepare_run_workspace() {
|
||||
local_debug_prepare_run_lifecycle_templates
|
||||
local_debug_seed_server_lifecycle_workspace server-local-debug game.example run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-alpha game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-beta game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace scum-dynamic game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace "$SERVER_LOCAL_ID" game.example run-local
|
||||
local_debug_seed_server_lifecycle_workspace "$SCUM_ALPHA_ID" game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace "$SCUM_BETA_ID" game.scum run-local
|
||||
local_debug_seed_server_lifecycle_workspace "$SCUM_DYNAMIC_ID" game.scum run-local
|
||||
}
|
||||
|
||||
prepare_run_workspace
|
||||
@@ -58,8 +79,55 @@ wait_for_url() {
|
||||
return 1
|
||||
}
|
||||
|
||||
assert_no_active_run_session() {
|
||||
local endpoint_file="$WORK_DIR/bootstrap-run-endpoint.response.json"
|
||||
local endpoint_url="${RUN_PLATFORM_URL%/}/api/v1/run/endpoints/$RUN_ENDPOINT_ID"
|
||||
local status
|
||||
status="$(curl -sS -o "$endpoint_file" -w '%{http_code}' "$endpoint_url" || true)"
|
||||
case "$status" in
|
||||
404)
|
||||
return 0
|
||||
;;
|
||||
200)
|
||||
if node - "$endpoint_file" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const endpoint = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const heartbeat = endpoint.lastHeartbeatAt || endpoint.LastHeartbeatAt;
|
||||
const ageMilliseconds = heartbeat ? Date.now() - Date.parse(heartbeat) : Number.POSITIVE_INFINITY;
|
||||
process.exit(endpoint.status === "online" && Number.isFinite(ageMilliseconds) && ageMilliseconds >= 0 && ageMilliseconds < 30_000 ? 0 : 1);
|
||||
NODE
|
||||
then
|
||||
printf 'refusing to self-start Run endpoint %s: an active session already exists at %s\n' "$RUN_ENDPOINT_ID" "$RUN_PLATFORM_URL" >&2
|
||||
printf 'run the smoke against that existing stack, or stop the existing Run before using LOCAL_DEBUG_SELF_START=true\n' >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
printf 'refusing to self-start Run endpoint %s: could not verify its session state at %s (HTTP %s)\n' "$RUN_ENDPOINT_ID" "$RUN_PLATFORM_URL" "$status" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_no_managed_run_process() {
|
||||
local run_pid_file="$LOCAL_DEBUG_PID_DIR/run.pid"
|
||||
if [[ ! -f "$run_pid_file" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local pid
|
||||
pid="$(<"$run_pid_file")"
|
||||
if [[ "$pid" =~ ^[0-9]+$ ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
printf 'refusing to self-start a second Run while managed Run pid %s is active\n' "$pid" >&2
|
||||
printf 'run the smoke against that existing stack, or stop it before using LOCAL_DEBUG_SELF_START=true\n' >&2
|
||||
return 1
|
||||
fi
|
||||
rm -f "$run_pid_file"
|
||||
}
|
||||
|
||||
start_self_hosted_stack() {
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$PLATFORM_DATA_DIR" "$PLATFORM_LOG_DIR" "$PLATFORM_ARTIFACT_DIR" "$RUN_WORKSPACE_ROOT" "$RUN_SPOOL_ROOT" "$RUN_BUILD_BUCKET_ROOT" "$GOCACHE"
|
||||
local_debug_prepare_distribution_builder
|
||||
trap cleanup_self_started EXIT
|
||||
|
||||
printf 'self-starting platform for local debug smoke\n'
|
||||
@@ -73,10 +141,16 @@ start_self_hosted_stack() {
|
||||
PLATFORM_METADATA_PATH="$PLATFORM_METADATA_PATH" \
|
||||
PLATFORM_LOG_BODY_BACKEND="$PLATFORM_LOG_BODY_BACKEND" \
|
||||
PLATFORM_LOG_DIR="$PLATFORM_LOG_DIR" \
|
||||
PLATFORM_ARTIFACT_DIR="$PLATFORM_ARTIFACT_DIR" \
|
||||
PLATFORM_BOOTSTRAP_ADMIN_EMAIL="$PLATFORM_BOOTSTRAP_ADMIN_EMAIL" \
|
||||
PLATFORM_BOOTSTRAP_ADMIN_PASSWORD="$PLATFORM_BOOTSTRAP_ADMIN_PASSWORD" \
|
||||
PLATFORM_SECRET_ENVELOPE_KEY="$PLATFORM_SECRET_ENVELOPE_KEY" \
|
||||
PLATFORM_ARTIFACT_DIR="$PLATFORM_ARTIFACT_DIR" \
|
||||
PLATFORM_BOOTSTRAP_ADMIN_EMAIL="$PLATFORM_BOOTSTRAP_ADMIN_EMAIL" \
|
||||
PLATFORM_BOOTSTRAP_ADMIN_PASSWORD="$PLATFORM_BOOTSTRAP_ADMIN_PASSWORD" \
|
||||
PLATFORM_SECRET_ENVELOPE_KEY="$PLATFORM_SECRET_ENVELOPE_KEY" \
|
||||
PLATFORM_RUN_RELEASE_URL="$PLATFORM_RUN_RELEASE_URL" \
|
||||
PLATFORM_BUILDER_DOCKER_BINARY="$PLATFORM_BUILDER_DOCKER_BINARY" \
|
||||
PLATFORM_BUILDER_IMAGE="$PLATFORM_BUILDER_IMAGE" \
|
||||
PLATFORM_BUILDER_SOURCE_DIR="$PLATFORM_BUILDER_SOURCE_DIR" \
|
||||
PLATFORM_BUILDER_WORKSPACE_DIR="$PLATFORM_BUILDER_WORKSPACE_DIR" \
|
||||
PLATFORM_BUILDER_TIMEOUT_SECONDS="$PLATFORM_BUILDER_TIMEOUT_SECONDS" \
|
||||
go run ./cmd/platform
|
||||
) >"$LOCAL_DEBUG_LOG_DIR/platform.log" 2>&1 &
|
||||
SELF_STARTED_PIDS+=("$!")
|
||||
@@ -402,6 +476,108 @@ if (reference.artifactId !== artifactId || reference.checksum !== checksum || pa
|
||||
NODE
|
||||
}
|
||||
|
||||
|
||||
local_debug_host_target() {
|
||||
local os_name
|
||||
local arch_name
|
||||
case "$(uname -s)" in
|
||||
Darwin) os_name="darwin" ;;
|
||||
Linux) os_name="linux" ;;
|
||||
*) printf 'unsupported local-debug generated Run host OS: %s\n' "$(uname -s)" >&2; return 1 ;;
|
||||
esac
|
||||
case "$(uname -m)" in
|
||||
arm64 | aarch64) arch_name="arm64" ;;
|
||||
x86_64 | amd64) arch_name="amd64" ;;
|
||||
*) printf 'unsupported local-debug generated Run host architecture: %s\n' "$(uname -m)" >&2; return 1 ;;
|
||||
esac
|
||||
printf '%s/%s' "$os_name" "$arch_name"
|
||||
}
|
||||
|
||||
launch_generated_run() {
|
||||
local payload_file="$1"
|
||||
mkdir -p "$LOCAL_DEBUG_LOG_DIR" "$LOCAL_DEBUG_PID_DIR" "$(dirname "$GENERATED_RUN_BIN")" "$RUN_SPOOL_ROOT/$GENERATED_RUN_ENDPOINT_ID"
|
||||
cp "$payload_file" "$GENERATED_RUN_BIN"
|
||||
chmod 700 "$GENERATED_RUN_BIN"
|
||||
(
|
||||
unset RUN_PLATFORM_URL RUN_ENDPOINT_ID RUN_DISPLAY_NAME RUN_VERSION RUN_REGISTRATION_TOKEN
|
||||
unset RUN_SERVER_INSTANCE_ID RUN_PLUGIN_ID RUN_COMPONENT_KIND RUN_COMPONENT_KEY RUN_KEY_GENERATION RUN_PACKAGE_CONFIG
|
||||
exec env \
|
||||
GOCACHE="$GOCACHE" \
|
||||
RUN_MODE=worker \
|
||||
RUN_WORKSPACE_ROOT="$RUN_WORKSPACE_ROOT" \
|
||||
RUN_BUILD_SOURCE_ROOT="$RUN_BUILD_SOURCE_ROOT" \
|
||||
RUN_SPOOL_ROOT="$RUN_SPOOL_ROOT/$GENERATED_RUN_ENDPOINT_ID" \
|
||||
RUN_MAX_JOBS="$RUN_MAX_JOBS" \
|
||||
RUN_HEARTBEAT_INTERVAL_MS="$RUN_HEARTBEAT_INTERVAL_MS" \
|
||||
RUN_POLL_INTERVAL_MS="$RUN_POLL_INTERVAL_MS" \
|
||||
RUN_RETRY_BACKOFF_MS="$RUN_RETRY_BACKOFF_MS" \
|
||||
"$GENERATED_RUN_BIN"
|
||||
) >"$GENERATED_RUN_LOG" 2>&1 &
|
||||
local generated_pid="$!"
|
||||
printf '%s' "$generated_pid" >"$GENERATED_RUN_PID_FILE"
|
||||
if [[ "${LOCAL_DEBUG_SELF_START:-false}" == "true" ]]; then
|
||||
SELF_STARTED_PIDS+=("$generated_pid")
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_generated_run_registration_and_heartbeat() {
|
||||
local registration_file="$WORK_DIR/generated-run-registration.response.json"
|
||||
local heartbeat_file="$WORK_DIR/generated-run-heartbeat.response.json"
|
||||
local first_heartbeat=""
|
||||
rm -f "$registration_file" "$heartbeat_file"
|
||||
printf 'waiting for generated Run endpoint %s registration\n' "$GENERATED_RUN_ENDPOINT_ID"
|
||||
for _ in $(seq 1 45); do
|
||||
if [[ -f "$GENERATED_RUN_PID_FILE" ]] && ! kill -0 "$(<"$GENERATED_RUN_PID_FILE")" 2>/dev/null; then
|
||||
printf 'generated Run exited before registration; see %s\n' "$GENERATED_RUN_LOG" >&2
|
||||
return 1
|
||||
fi
|
||||
if json_get "$API_URL/run/endpoints/$GENERATED_RUN_ENDPOINT_ID" "$registration_file" "${AUTH_HEADER[@]}" 2>/dev/null; then
|
||||
if first_heartbeat="$(node - "$registration_file" "$GENERATED_RUN_ENDPOINT_ID" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const endpoint = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const expectedID = process.argv[3];
|
||||
const heartbeat = endpoint.lastHeartbeatAt || endpoint.LastHeartbeatAt || "";
|
||||
const capabilities = endpoint.capabilities || endpoint.Capabilities || [];
|
||||
if (endpoint.id !== expectedID || endpoint.status !== "online" || !heartbeat || capabilities.includes("distribution.build")) {
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(heartbeat);
|
||||
NODE
|
||||
)"; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [[ -z "$first_heartbeat" ]]; then
|
||||
printf 'generated Run endpoint %s did not register with a safe capability report\n' "$GENERATED_RUN_ENDPOINT_ID" >&2
|
||||
[[ -f "$GENERATED_RUN_LOG" ]] && sed -n '1,160p' "$GENERATED_RUN_LOG" >&2
|
||||
return 1
|
||||
fi
|
||||
reject_forbidden_fragments "$registration_file"
|
||||
|
||||
printf 'waiting for generated Run endpoint %s heartbeat\n' "$GENERATED_RUN_ENDPOINT_ID"
|
||||
for _ in $(seq 1 45); do
|
||||
if json_get "$API_URL/run/endpoints/$GENERATED_RUN_ENDPOINT_ID" "$heartbeat_file" "${AUTH_HEADER[@]}" 2>/dev/null && node - "$heartbeat_file" "$GENERATED_RUN_ENDPOINT_ID" "$first_heartbeat" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const endpoint = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const expectedID = process.argv[3];
|
||||
const firstHeartbeat = Date.parse(process.argv[4]);
|
||||
const heartbeat = Date.parse(endpoint.lastHeartbeatAt || endpoint.LastHeartbeatAt || "");
|
||||
const capabilities = endpoint.capabilities || endpoint.Capabilities || [];
|
||||
process.exit(endpoint.id === expectedID && endpoint.status === "online" && Number.isFinite(heartbeat) && heartbeat > firstHeartbeat && !capabilities.includes("distribution.build") ? 0 : 1);
|
||||
NODE
|
||||
then
|
||||
reject_forbidden_fragments "$heartbeat_file"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
printf 'generated Run endpoint %s did not report a subsequent heartbeat\n' "$GENERATED_RUN_ENDPOINT_ID" >&2
|
||||
[[ -f "$GENERATED_RUN_LOG" ]] && sed -n '1,160p' "$GENERATED_RUN_LOG" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_for_lifecycle_install_success() {
|
||||
local server_id="$1"
|
||||
local output_file="$2"
|
||||
@@ -476,7 +652,7 @@ const manifest = {
|
||||
createFormSchema: source.server.createFormSchema
|
||||
},
|
||||
capabilities: ["process.install", "process.start", "process.stop", "config.write"],
|
||||
permissions: ["server.read", "server.lifecycle", "server.logs.read", "server.artifacts.read", "ai.invoke"],
|
||||
permissions: ["server.read", "server.lifecycle", "server.run.distribution", "server.logs.read", "server.artifacts.read", "ai.invoke"],
|
||||
actions: {
|
||||
install: source.actions.install,
|
||||
start: source.actions.start,
|
||||
@@ -488,11 +664,11 @@ const manifest = {
|
||||
key: "logs",
|
||||
title: "Logs",
|
||||
path: "/logs",
|
||||
permissions: ["server.read", "server.lifecycle", "server.logs.read", "server.artifacts.read", "ai.invoke"],
|
||||
bridgeActions: ["server.instances.read", "jobs.dispatch", "logs.query", "artifacts.open", "plugin-lifecycle.request", "ai.invoke"]
|
||||
permissions: ["server.read", "server.lifecycle", "server.run.distribution", "server.logs.read", "server.artifacts.read", "ai.invoke"],
|
||||
bridgeActions: ["server.instances.read", "jobs.dispatch", "logs.query", "artifacts.open", "run.distribution.request", "plugin-lifecycle.request", "ai.invoke"]
|
||||
}
|
||||
],
|
||||
bridge: { actions: ["server.instances.read", "jobs.dispatch", "logs.query", "artifacts.open", "plugin-lifecycle.request", "ai.invoke"] },
|
||||
bridge: { actions: ["server.instances.read", "jobs.dispatch", "logs.query", "artifacts.open", "run.distribution.request", "plugin-lifecycle.request", "ai.invoke"] },
|
||||
ai: source.ai,
|
||||
productionLifecycle: source.productionLifecycle,
|
||||
runtimeProfiles: {
|
||||
@@ -595,25 +771,45 @@ done
|
||||
require_file_contains "$WORK_DIR/run-endpoints.response.json" "$RUN_ENDPOINT_ID"
|
||||
reject_forbidden_fragments "$WORK_DIR/run-endpoints.response.json"
|
||||
|
||||
GENERATED_RUN_TARGET="$(local_debug_host_target)"
|
||||
IFS=/ read -r GENERATED_RUN_TARGET_OS GENERATED_RUN_TARGET_ARCH <<<"$GENERATED_RUN_TARGET"
|
||||
printf 'GENERATED_RUN_TARGET_OS=%s\nGENERATED_RUN_TARGET_ARCH=%s\n' "$GENERATED_RUN_TARGET_OS" "$GENERATED_RUN_TARGET_ARCH" >>"$WORK_DIR/run-build-config.env"
|
||||
|
||||
cat >"$WORK_DIR/create-server.request.json" <<JSON
|
||||
{
|
||||
"id": "server-local-debug",
|
||||
"id": "$SERVER_LOCAL_ID",
|
||||
"pluginId": "game.example",
|
||||
"runEndpointId": "$RUN_ENDPOINT_ID",
|
||||
"name": "Local Debug Example Server",
|
||||
"idempotencyKey": "local-debug-create",
|
||||
"name": "Local Debug Example Server $SMOKE_INVOCATION_ID",
|
||||
"idempotencyKey": "local-debug-create-$SMOKE_INVOCATION_ID"
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/server-runtime-binding.request.json" <<JSON
|
||||
{
|
||||
"profileKey": "run-local",
|
||||
"bindings": {}
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/server-run-generate.request.json" <<JSON
|
||||
{
|
||||
"targetOs": "$GENERATED_RUN_TARGET_OS",
|
||||
"targetArch": "$GENERATED_RUN_TARGET_ARCH",
|
||||
"idempotencyKey": "local-debug-server-run-generate-$SMOKE_INVOCATION_ID"
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/create-scum-alpha.request.json" <<JSON
|
||||
{
|
||||
"id": "scum-alpha",
|
||||
"id": "$SCUM_ALPHA_ID",
|
||||
"pluginId": "game.scum",
|
||||
"runEndpointId": "$RUN_ENDPOINT_ID",
|
||||
"name": "SCUM Alpha",
|
||||
"idempotencyKey": "local-debug-scum-alpha-create",
|
||||
"name": "SCUM Alpha $SMOKE_INVOCATION_ID",
|
||||
"idempotencyKey": "local-debug-scum-alpha-create-$SMOKE_INVOCATION_ID"
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/scum-alpha-runtime-binding.request.json" <<JSON
|
||||
{
|
||||
"profileKey": "run-local",
|
||||
"bindings": {}
|
||||
}
|
||||
@@ -621,11 +817,11 @@ JSON
|
||||
|
||||
cat >"$WORK_DIR/create-scum-beta.request.json" <<JSON
|
||||
{
|
||||
"id": "scum-beta",
|
||||
"id": "$SCUM_BETA_ID",
|
||||
"pluginId": "game.scum",
|
||||
"runEndpointId": "$RUN_ENDPOINT_ID",
|
||||
"name": "SCUM Beta",
|
||||
"idempotencyKey": "local-debug-scum-beta-create",
|
||||
"name": "SCUM Beta $SMOKE_INVOCATION_ID",
|
||||
"idempotencyKey": "local-debug-scum-beta-create-$SMOKE_INVOCATION_ID",
|
||||
"profileKey": "run-local",
|
||||
"bindings": {}
|
||||
}
|
||||
@@ -633,11 +829,11 @@ JSON
|
||||
|
||||
cat >"$WORK_DIR/create-scum-dynamic.request.json" <<JSON
|
||||
{
|
||||
"id": "scum-dynamic",
|
||||
"id": "$SCUM_DYNAMIC_ID",
|
||||
"pluginId": "game.scum",
|
||||
"runEndpointId": "$RUN_ENDPOINT_ID",
|
||||
"name": "SCUM Dynamic",
|
||||
"idempotencyKey": "local-debug-scum-dynamic-create",
|
||||
"name": "SCUM Dynamic $SMOKE_INVOCATION_ID",
|
||||
"idempotencyKey": "local-debug-scum-dynamic-create-$SMOKE_INVOCATION_ID",
|
||||
"profileKey": "run-local",
|
||||
"bindings": {}
|
||||
}
|
||||
@@ -645,20 +841,20 @@ JSON
|
||||
|
||||
cat >"$WORK_DIR/scum-alpha-run-generate.request.json" <<JSON
|
||||
{
|
||||
"targetOs": "windows",
|
||||
"targetOs": "linux",
|
||||
"targetArch": "amd64",
|
||||
"idempotencyKey": "local-debug-scum-alpha-run-generate"
|
||||
"idempotencyKey": "local-debug-scum-alpha-run-generate-$SMOKE_INVOCATION_ID"
|
||||
}
|
||||
JSON
|
||||
|
||||
printf 'creating server lifecycle workflow through platform API\n'
|
||||
create_server_workflow "dev" "server-local-debug" "$WORK_DIR/create-server.request.json" "$WORK_DIR/create-server.response.json"
|
||||
create_server_workflow "dev" "$SERVER_LOCAL_ID" "$WORK_DIR/create-server.request.json" "$WORK_DIR/create-server.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-server.response.json"
|
||||
|
||||
printf 'creating SCUM server lifecycle workflows through platform API\n'
|
||||
create_server_workflow "SCUM alpha" "scum-alpha" "$WORK_DIR/create-scum-alpha.request.json" "$WORK_DIR/create-scum-alpha.response.json"
|
||||
create_server_workflow "SCUM beta" "scum-beta" "$WORK_DIR/create-scum-beta.request.json" "$WORK_DIR/create-scum-beta.response.json"
|
||||
create_server_workflow "SCUM dynamic" "scum-dynamic" "$WORK_DIR/create-scum-dynamic.request.json" "$WORK_DIR/create-scum-dynamic.response.json"
|
||||
create_server_workflow "SCUM alpha" "$SCUM_ALPHA_ID" "$WORK_DIR/create-scum-alpha.request.json" "$WORK_DIR/create-scum-alpha.response.json"
|
||||
create_server_workflow "SCUM beta" "$SCUM_BETA_ID" "$WORK_DIR/create-scum-beta.request.json" "$WORK_DIR/create-scum-beta.response.json"
|
||||
create_server_workflow "SCUM dynamic" "$SCUM_DYNAMIC_ID" "$WORK_DIR/create-scum-dynamic.request.json" "$WORK_DIR/create-scum-dynamic.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-alpha.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-beta.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/create-scum-dynamic.response.json"
|
||||
@@ -667,9 +863,114 @@ require_file_contains "$WORK_DIR/create-scum-beta.response.json" '"pluginId"[[:s
|
||||
require_file_contains "$WORK_DIR/create-scum-dynamic.response.json" '"pluginId"[[:space:]]*:[[:space:]]*"game.scum"'
|
||||
|
||||
SERVER_ID="$(json_id "$WORK_DIR/create-server.response.json")"
|
||||
SCUM_ALPHA_ID="$(json_id "$WORK_DIR/create-scum-alpha.response.json")"
|
||||
SCUM_BETA_ID="$(json_id "$WORK_DIR/create-scum-beta.response.json")"
|
||||
SCUM_DYNAMIC_ID="$(json_id "$WORK_DIR/create-scum-dynamic.response.json")"
|
||||
CREATED_SCUM_ALPHA_ID="$(json_id "$WORK_DIR/create-scum-alpha.response.json")"
|
||||
CREATED_SCUM_BETA_ID="$(json_id "$WORK_DIR/create-scum-beta.response.json")"
|
||||
CREATED_SCUM_DYNAMIC_ID="$(json_id "$WORK_DIR/create-scum-dynamic.response.json")"
|
||||
if [[ "$SERVER_ID" != "$SERVER_LOCAL_ID" || "$CREATED_SCUM_ALPHA_ID" != "$SCUM_ALPHA_ID" || "$CREATED_SCUM_BETA_ID" != "$SCUM_BETA_ID" || "$CREATED_SCUM_DYNAMIC_ID" != "$SCUM_DYNAMIC_ID" ]]; then
|
||||
printf 'created server IDs do not match invocation-scoped workspace IDs\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node - "$WORK_DIR/create-server.request.json" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const request = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
for (const forbidden of ["deploymentTargetId", "runEndpointId", "profileKey", "bindings", "deployment"]) {
|
||||
if (Object.hasOwn(request, forbidden)) {
|
||||
console.error(`minimal server creation unexpectedly included ${forbidden}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
if (!request.pluginId || !request.name) {
|
||||
console.error("minimal server creation omitted pluginId or name");
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
|
||||
printf 'configuring example server runtime binding after minimal creation\n'
|
||||
curl -fsS -X PUT -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" \
|
||||
--data-binary "@$WORK_DIR/server-runtime-binding.request.json" \
|
||||
"$API_URL/server-instances/$SERVER_ID/runtime-binding" >"$WORK_DIR/server-runtime-binding.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/server-runtime-binding.response.json"
|
||||
require_file_contains "$WORK_DIR/server-runtime-binding.response.json" '"status"[[:space:]]*:[[:space:]]*"complete"'
|
||||
|
||||
printf 'checking example server platform-builder action\n'
|
||||
json_get "$API_URL/server-instances/$SERVER_ID/runtime/actions" "$WORK_DIR/server-runtime-actions.response.json" "${AUTH_HEADER[@]}"
|
||||
node - "$WORK_DIR/server-runtime-actions.response.json" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const response = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const action = (response.actions || []).find((candidate) => candidate.key === "generate-run");
|
||||
if (!action || action.available !== true || (action.reason || "").includes("run endpoint")) {
|
||||
console.error("expected minimal example server to generate through the ready platform builder");
|
||||
console.error(JSON.stringify(response, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
reject_forbidden_fragments "$WORK_DIR/server-runtime-actions.response.json"
|
||||
|
||||
printf 'generating host-native example Run through platform Docker builder\n'
|
||||
curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/server-run-generate.request.json" "$API_URL/server-instances/$SERVER_ID/run/generate" >"$WORK_DIR/server-run-generate.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/server-run-generate.response.json"
|
||||
node - "$WORK_DIR/server-run-generate.response.json" "$SERVER_ID" "$GENERATED_RUN_ENDPOINT_ID" "$GENERATED_RUN_TARGET_OS" "$GENERATED_RUN_TARGET_ARCH" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const distribution = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const [serverID, endpointID, targetOS, targetArch] = process.argv.slice(3);
|
||||
if (distribution.serverInstanceId !== serverID || distribution.runEndpointId !== endpointID || distribution.targetOs !== targetOS || distribution.targetArch !== targetArch || !distribution.buildJobId || !distribution.artifactId) {
|
||||
console.error("host-native Run distribution identity did not match the minimal server");
|
||||
console.error(JSON.stringify(distribution, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
wait_for_distribution_build "$WORK_DIR/server-run-generate.response.json" "$WORK_DIR/server-run-build-job.response.json" "$WORK_DIR/server-run-build-artifact.response.json"
|
||||
node - "$WORK_DIR/server-run-build-job.response.json" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
if (job.runEndpointId !== "platform-distribution-builder" || job.capability !== "distribution.build" || job.state !== "succeeded") {
|
||||
console.error("host-native Run was not completed by the platform distribution builder");
|
||||
console.error(JSON.stringify(job, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
reject_forbidden_fragments "$WORK_DIR/server-run-build-job.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/server-run-build-artifact.response.json"
|
||||
read_latest_run_download_content "$SERVER_ID" "$WORK_DIR/server-run-download.response.json" "$WORK_DIR/server-run-download-content.bin" "$WORK_DIR/server-run-download-chunks"
|
||||
launch_generated_run "$WORK_DIR/server-run-download-content.bin"
|
||||
wait_for_generated_run_registration_and_heartbeat
|
||||
|
||||
cat >"$WORK_DIR/server-deployment.request.json" <<JSON
|
||||
{
|
||||
"runEndpointId": "$GENERATED_RUN_ENDPOINT_ID",
|
||||
"mode": "custom-command",
|
||||
"profileKey": "run-local",
|
||||
"createInputs": {},
|
||||
"serverRoot": "$RUN_WORKSPACE_ROOT/$SERVER_ID",
|
||||
"startCommand": "/usr/bin/true"
|
||||
}
|
||||
JSON
|
||||
printf 'saving optional deployment settings after generated Run registration\n'
|
||||
curl -fsS -X PUT -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" \
|
||||
--data-binary "@$WORK_DIR/server-deployment.request.json" \
|
||||
"$API_URL/server-instances/$SERVER_ID/deployment" >"$WORK_DIR/server-deployment.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/server-deployment.response.json"
|
||||
|
||||
json_get "$API_URL/server-instances/$SERVER_ID" "$WORK_DIR/server-before-deploy.response.json" "${AUTH_HEADER[@]}"
|
||||
SERVER_CONFIG_VERSION="$(node -e 'const fs=require("fs"); const value=JSON.parse(fs.readFileSync(process.argv[1], "utf8")).configVersion; if (!Number.isInteger(value) || value < 1) process.exit(2); process.stdout.write(String(value));' "$WORK_DIR/server-before-deploy.response.json")"
|
||||
cat >"$WORK_DIR/server-deploy.request.json" <<JSON
|
||||
{
|
||||
"expectedConfigVersion": $SERVER_CONFIG_VERSION,
|
||||
"idempotencyKey": "local-debug-server-deploy-$SMOKE_INVOCATION_ID"
|
||||
}
|
||||
JSON
|
||||
json_post "$API_URL/server-instances/$SERVER_ID/deploy" "$WORK_DIR/server-deploy.request.json" "$WORK_DIR/server-deploy.response.json" "${AUTH_HEADER[@]}"
|
||||
reject_forbidden_fragments "$WORK_DIR/server-deploy.response.json"
|
||||
require_file_contains "$WORK_DIR/server-deploy.response.json" "\"runEndpointId\"[[:space:]]*:[[:space:]]*\"$GENERATED_RUN_ENDPOINT_ID\""
|
||||
wait_for_lifecycle_install_success "$SERVER_ID" "$WORK_DIR/jobs.response.json"
|
||||
|
||||
printf 'configuring SCUM alpha runtime binding after minimal creation\n'
|
||||
curl -fsS -X PUT -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" \
|
||||
--data-binary "@$WORK_DIR/scum-alpha-runtime-binding.request.json" \
|
||||
"$API_URL/server-instances/$SCUM_ALPHA_ID/runtime-binding" >"$WORK_DIR/scum-alpha-runtime-binding.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-runtime-binding.response.json"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-runtime-binding.response.json" '"status"[[:space:]]*:[[:space:]]*"complete"'
|
||||
|
||||
wait_for_lifecycle_install_success "$SCUM_DYNAMIC_ID" "$WORK_DIR/scum-dynamic-jobs.response.json"
|
||||
|
||||
@@ -677,27 +978,29 @@ printf 'checking SCUM runtime distribution action\n'
|
||||
json_get "$API_URL/server-instances/$SCUM_ALPHA_ID/runtime/actions" "$WORK_DIR/scum-alpha-runtime-actions.response.json" "${AUTH_HEADER[@]}"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-runtime-actions.response.json"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-runtime-actions.response.json" '"key"[[:space:]]*:[[:space:]]*"generate-run"'
|
||||
SCUM_BUILD_AVAILABLE="$(node - "$WORK_DIR/scum-alpha-runtime-actions.response.json" <<'NODE'
|
||||
node - "$WORK_DIR/scum-alpha-runtime-actions.response.json" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const response = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const action = (response.actions || []).find((candidate) => candidate.key === "generate-run");
|
||||
if (!action || (action.available !== true && action.reason !== "run endpoint cannot build distributions")) {
|
||||
console.error("expected SCUM generate-run action to match the Run capability report");
|
||||
if (!action || action.available !== true) {
|
||||
console.error("expected SCUM generate-run action to use the ready platform builder");
|
||||
console.error(JSON.stringify(response, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(action.available === true ? "true" : "false");
|
||||
if ((action.reason || "").includes("run endpoint")) {
|
||||
console.error("platform build availability must not reference a Run endpoint capability");
|
||||
console.error(JSON.stringify(action, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
)"
|
||||
|
||||
if [[ "$SCUM_BUILD_AVAILABLE" == "true" ]]; then
|
||||
printf 'generating SCUM run package through platform API\n'
|
||||
curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/scum-alpha-run-generate.request.json" "$API_URL/server-instances/$SCUM_ALPHA_ID/run/generate" >"$WORK_DIR/scum-alpha-run-generate.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-generate.response.json"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" '"serverInstanceId"[[:space:]]*:[[:space:]]*"scum-alpha"'
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" '"artifactId"[[:space:]]*:[[:space:]]*"artifact-run-dist-scum-alpha'
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" '"buildJobId"[[:space:]]*:[[:space:]]*"job-distribution-build'
|
||||
node - "$WORK_DIR/scum-alpha-run-generate.response.json" <<'NODE'
|
||||
printf 'generating SCUM run package through platform API\n'
|
||||
curl -fsS -H 'Content-Type: application/json' "${AUTH_HEADER[@]}" --data-binary "@$WORK_DIR/scum-alpha-run-generate.request.json" "$API_URL/server-instances/$SCUM_ALPHA_ID/run/generate" >"$WORK_DIR/scum-alpha-run-generate.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-generate.response.json"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" "\"serverInstanceId\"[[:space:]]*:[[:space:]]*\"$SCUM_ALPHA_ID\""
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" "\"artifactId\"[[:space:]]*:[[:space:]]*\"artifact-run-dist-$SCUM_ALPHA_ID"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-run-generate.response.json" '"buildJobId"[[:space:]]*:[[:space:]]*"job-distribution-build'
|
||||
node - "$WORK_DIR/scum-alpha-run-generate.response.json" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const distribution = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
if (distribution.status !== "building" && distribution.status !== "available") {
|
||||
@@ -706,14 +1009,11 @@ if (distribution.status !== "building" && distribution.status !== "available") {
|
||||
process.exit(1);
|
||||
}
|
||||
NODE
|
||||
wait_for_distribution_build "$WORK_DIR/scum-alpha-run-generate.response.json" "$WORK_DIR/scum-alpha-run-build-job.response.json" "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-job.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
read_latest_run_download_content "$SCUM_ALPHA_ID" "$WORK_DIR/scum-alpha-run-download.response.json" "$WORK_DIR/scum-alpha-run-download-content.bin" "$WORK_DIR/scum-alpha-run-download-chunks"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-download.response.json"
|
||||
else
|
||||
printf 'SCUM run package build is unavailable because Run did not advertise distribution.build; verified without fake success\n'
|
||||
fi
|
||||
wait_for_distribution_build "$WORK_DIR/scum-alpha-run-generate.response.json" "$WORK_DIR/scum-alpha-run-build-job.response.json" "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-job.response.json"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-build-artifact.response.json"
|
||||
read_latest_run_download_content "$SCUM_ALPHA_ID" "$WORK_DIR/scum-alpha-run-download.response.json" "$WORK_DIR/scum-alpha-run-download-content.bin" "$WORK_DIR/scum-alpha-run-download-chunks"
|
||||
reject_forbidden_fragments "$WORK_DIR/scum-alpha-run-download.response.json"
|
||||
|
||||
printf 'checking jobs, logs, artifacts, and marketplace refs\n'
|
||||
json_get "$API_URL/server-instances" "$WORK_DIR/server-instances.response.json" "${AUTH_HEADER[@]}"
|
||||
@@ -726,14 +1026,34 @@ json_get "$API_URL/artifacts" "$WORK_DIR/artifacts.response.json" "${AUTH_HEADER
|
||||
json_get "$API_URL/plugin-marketplace/plugins" "$WORK_DIR/marketplace.response.json" "${AUTH_HEADER[@]}"
|
||||
json_get "$API_URL/plugin-marketplace/plugins?serverType=scum&keyword=scum" "$WORK_DIR/scum-marketplace.response.json" "${AUTH_HEADER[@]}"
|
||||
require_file_contains "$WORK_DIR/scum-marketplace.response.json" '"id"[[:space:]]*:[[:space:]]*"game.scum"'
|
||||
if [[ "$SCUM_BUILD_AVAILABLE" == "true" ]]; then
|
||||
require_file_contains "$WORK_DIR/artifacts.response.json" '"id"[[:space:]]*:[[:space:]]*"artifact-run-dist-scum-alpha'
|
||||
fi
|
||||
require_file_contains "$WORK_DIR/scum-alpha-jobs.response.json" '"serverInstanceId"[[:space:]]*:[[:space:]]*"scum-alpha"'
|
||||
require_file_contains "$WORK_DIR/scum-beta-jobs.response.json" '"serverInstanceId"[[:space:]]*:[[:space:]]*"scum-beta"'
|
||||
require_file_contains "$WORK_DIR/artifacts.response.json" "\"id\"[[:space:]]*:[[:space:]]*\"artifact-run-dist-$SCUM_ALPHA_ID"
|
||||
require_file_contains "$WORK_DIR/scum-alpha-jobs.response.json" "\"serverInstanceId\"[[:space:]]*:[[:space:]]*\"$SCUM_ALPHA_ID\""
|
||||
require_file_contains "$WORK_DIR/scum-beta-jobs.response.json" "\"serverInstanceId\"[[:space:]]*:[[:space:]]*\"$SCUM_BETA_ID\""
|
||||
require_file_contains "$WORK_DIR/scum-dynamic-jobs.response.json" '"state"[[:space:]]*:[[:space:]]*"succeeded"'
|
||||
|
||||
for file in "$WORK_DIR"/server-instances.response.json "$WORK_DIR"/jobs.response.json "$WORK_DIR"/scum-alpha-jobs.response.json "$WORK_DIR"/scum-beta-jobs.response.json "$WORK_DIR"/scum-dynamic-jobs.response.json "$WORK_DIR"/log-streams.response.json "$WORK_DIR"/artifacts.response.json "$WORK_DIR"/marketplace.response.json "$WORK_DIR"/scum-marketplace.response.json "$WORK_DIR"/scum-alpha-runtime-actions.response.json "$WORK_DIR"/scum-alpha-run-generate.response.json "$WORK_DIR"/scum-alpha-run-download.response.json; do
|
||||
for file in \
|
||||
"$WORK_DIR"/server-instances.response.json \
|
||||
"$WORK_DIR"/jobs.response.json \
|
||||
"$WORK_DIR"/server-runtime-binding.response.json \
|
||||
"$WORK_DIR"/server-runtime-actions.response.json \
|
||||
"$WORK_DIR"/server-run-generate.response.json \
|
||||
"$WORK_DIR"/server-run-build-job.response.json \
|
||||
"$WORK_DIR"/server-run-build-artifact.response.json \
|
||||
"$WORK_DIR"/server-run-download.response.json \
|
||||
"$WORK_DIR"/generated-run-registration.response.json \
|
||||
"$WORK_DIR"/generated-run-heartbeat.response.json \
|
||||
"$WORK_DIR"/server-deployment.response.json \
|
||||
"$WORK_DIR"/server-deploy.response.json \
|
||||
"$WORK_DIR"/scum-alpha-jobs.response.json \
|
||||
"$WORK_DIR"/scum-beta-jobs.response.json \
|
||||
"$WORK_DIR"/scum-dynamic-jobs.response.json \
|
||||
"$WORK_DIR"/log-streams.response.json \
|
||||
"$WORK_DIR"/artifacts.response.json \
|
||||
"$WORK_DIR"/marketplace.response.json \
|
||||
"$WORK_DIR"/scum-marketplace.response.json \
|
||||
"$WORK_DIR"/scum-alpha-runtime-actions.response.json \
|
||||
"$WORK_DIR"/scum-alpha-run-generate.response.json \
|
||||
"$WORK_DIR"/scum-alpha-run-download.response.json; do
|
||||
if [[ -f "$file" ]]; then
|
||||
reject_forbidden_fragments "$file"
|
||||
fi
|
||||
@@ -743,6 +1063,10 @@ if [[ "$VITE_PLATFORM_API_BASE_URL" != "/api/v1" ]]; then
|
||||
printf 'VITE_PLATFORM_API_BASE_URL must be /api/v1, got %s\n' "$VITE_PLATFORM_API_BASE_URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$PLATFORM_RUN_RELEASE_URL" != "$PLATFORM_URL" ]]; then
|
||||
printf 'PLATFORM_RUN_RELEASE_URL must be %s for executable local Run proof, got %s\n' "$PLATFORM_URL" "$PLATFORM_RUN_RELEASE_URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$PLATFORM_API_PROXY" != "$PLATFORM_URL" ]]; then
|
||||
printf 'PLATFORM_API_PROXY must be %s, got %s\n' "$PLATFORM_URL" "$PLATFORM_API_PROXY" >&2
|
||||
exit 1
|
||||
@@ -759,8 +1083,8 @@ cat >"$WORK_DIR/local-ui-checklist.md" <<EOF
|
||||
- Login with operator.local@example.test / operator-local.
|
||||
- Confirm the login path is API-backed and no local fallback banner or fallback workspace appears.
|
||||
- Visit 首页, 服务器管理, 插件市场, 用户管理, AI 提供商管理.
|
||||
- Open server-local-debug detail and inspect lifecycle history, plugin controls, logs, and artifact references.
|
||||
- Confirm 插件市场 can find SCUM Server / game.scum, then open scum-alpha, scum-beta, and scum-dynamic from 服务器管理.
|
||||
- Open $SERVER_ID detail and inspect lifecycle history, plugin controls, logs, and artifact references.
|
||||
- Confirm 插件市场 can find SCUM Server / game.scum, then open $SCUM_ALPHA_ID, $SCUM_BETA_ID, and $SCUM_DYNAMIC_ID from 服务器管理.
|
||||
- Confirm all SCUM servers are backed by game.scum and have separate lifecycle install jobs and operation history.
|
||||
- Search visible text for forbidden fragments: /Users/, /private/, unix://, tcp://, Bearer , sk-, password=, apiKeyRef, rawApiKey, run session tokens, direct run URLs, plugin-owned transport details.
|
||||
- Acceptance requires platform routes, logical IDs, job refs, log refs, artifact refs, and safe metadata only.
|
||||
|
||||
Reference in New Issue
Block a user