2026-05-18 22:22:22 +08:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
2026-05-27 09:39:05 +08:00
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
2026-05-18 22:22:22 +08:00
|
|
|
|
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
|
|
|
|
|
ARTIFACT_DIR="${ARTIFACT_DIR:-$ROOT_DIR/artifacts/real-host-acceptance/$TIMESTAMP}"
|
|
|
|
|
|
DRY_RUN="${DRY_RUN:-0}"
|
|
|
|
|
|
SKIP_ROLLBACK="${SKIP_ROLLBACK:-0}"
|
2026-05-25 10:48:04 +08:00
|
|
|
|
ARTIFACT_SECURITY_MODE="${ARTIFACT_SECURITY_MODE:-safe}"
|
|
|
|
|
|
ARTIFACT_INCLUDE_SECRETS="${ARTIFACT_INCLUDE_SECRETS:-0}"
|
2026-05-18 22:22:22 +08:00
|
|
|
|
|
|
|
|
|
|
require_var() {
|
|
|
|
|
|
local name="$1"
|
|
|
|
|
|
if [[ -z "${!name:-}" ]]; then
|
|
|
|
|
|
echo "missing required env: $name" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
json_get() {
|
|
|
|
|
|
local key="$1"
|
|
|
|
|
|
python3 -c 'import json, sys
|
|
|
|
|
|
key = sys.argv[1]
|
|
|
|
|
|
data = json.load(sys.stdin)
|
|
|
|
|
|
value = data
|
|
|
|
|
|
for part in key.split("."):
|
|
|
|
|
|
if isinstance(value, dict):
|
|
|
|
|
|
value = value.get(part)
|
|
|
|
|
|
else:
|
|
|
|
|
|
value = None
|
|
|
|
|
|
break
|
|
|
|
|
|
if value is None:
|
|
|
|
|
|
sys.exit(2)
|
|
|
|
|
|
if isinstance(value, (dict, list)):
|
|
|
|
|
|
print(json.dumps(value, ensure_ascii=False))
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(value)
|
|
|
|
|
|
' "$key"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
save_json() {
|
|
|
|
|
|
local name="$1"
|
|
|
|
|
|
local payload="$2"
|
|
|
|
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
|
|
|
|
printf '%s\n' "$payload" > "$ARTIFACT_DIR/$name.json"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 10:48:04 +08:00
|
|
|
|
artifact_redact_key_json() {
|
|
|
|
|
|
local value="$1"
|
2026-05-27 09:39:05 +08:00
|
|
|
|
python3 "$ROOT_DIR/scripts/acceptance/artifact_redaction.py" redact-key "$value"
|
2026-05-25 10:48:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 15:45:55 +08:00
|
|
|
|
write_checklist_guide() {
|
|
|
|
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
|
|
|
|
cat > "$ARTIFACT_DIR/00-artifact-guide.txt" <<EOF
|
|
|
|
|
|
真实宿主验收产物 -> 速查清单对应
|
|
|
|
|
|
|
2026-05-25 10:48:04 +08:00
|
|
|
|
artifact security mode: $ARTIFACT_SECURITY_MODE
|
|
|
|
|
|
contains raw secrets: $( [[ "$ARTIFACT_INCLUDE_SECRETS" == "1" ]] && printf 'yes' || printf 'no' )
|
|
|
|
|
|
repository-safe: $( [[ "$ARTIFACT_SECURITY_MODE" == "safe" && "$ARTIFACT_INCLUDE_SECRETS" != "1" ]] && printf 'yes' || printf 'no' )
|
|
|
|
|
|
|
2026-05-21 15:45:55 +08:00
|
|
|
|
清单 1(环境 / host 前置)
|
|
|
|
|
|
- 01-create-host.json
|
|
|
|
|
|
- 02-probe-host.json
|
|
|
|
|
|
|
|
|
|
|
|
清单 2(channel 宿主契约 / 导入落库)
|
|
|
|
|
|
- 03-install-pack.json
|
|
|
|
|
|
- 04-preview-import.json
|
|
|
|
|
|
- 05-import.json
|
|
|
|
|
|
- 05a-batch-detail-pre-access.json(若拿到 batch_id 且非 dry-run)
|
|
|
|
|
|
- 08-provider-status.json
|
|
|
|
|
|
- 09-reconcile.json
|
|
|
|
|
|
- 10-batch-detail.json(若拿到 batch_id 且非 dry-run)
|
|
|
|
|
|
|
|
|
|
|
|
清单 3(access / key 闭环状态)
|
|
|
|
|
|
- 06-access-preview.json
|
|
|
|
|
|
- 07-access-status.json
|
|
|
|
|
|
|
|
|
|
|
|
清单 4(必须分层留证据,不可混用)
|
|
|
|
|
|
- account 视角:由 AFTER_IMPORT_HOOK_COMMAND 额外落证据,例如 GET /api/v1/admin/accounts/:id/models
|
|
|
|
|
|
- 普通用户 / managed key 视角:由 AFTER_IMPORT_HOOK_COMMAND 额外落证据,例如 GET /v1/models
|
|
|
|
|
|
- completion 视角:由 AFTER_IMPORT_HOOK_COMMAND 额外落证据,例如 POST /v1/chat/completions
|
|
|
|
|
|
|
|
|
|
|
|
红线:
|
|
|
|
|
|
- /api/v1/admin/accounts/:id/models 正确 ≠ /v1/models 正确
|
|
|
|
|
|
- /v1/models 正确 ≠ /v1/chat/completions 正确
|
|
|
|
|
|
- admin API 成功 ≠ 普通用户链路成功
|
|
|
|
|
|
|
|
|
|
|
|
当前 hook 配置:$( [[ -n "$AFTER_IMPORT_HOOK_COMMAND" ]] && printf 'enabled' || printf 'disabled' )
|
|
|
|
|
|
EOF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_artifact_summary() {
|
|
|
|
|
|
echo "artifact guide: $ARTIFACT_DIR/00-artifact-guide.txt"
|
|
|
|
|
|
echo "checklist import evidence: 04-preview-import.json 05-import.json 05a-batch-detail-pre-access.json(optional) 08-provider-status.json 09-reconcile.json"
|
|
|
|
|
|
echo "checklist access evidence: 06-access-preview.json 07-access-status.json"
|
|
|
|
|
|
if [[ -n "$AFTER_IMPORT_HOOK_COMMAND" ]]; then
|
|
|
|
|
|
echo "checklist layered evidence: see 05b-after-import-hook.stdout.txt / 05b-after-import-hook.stderr.txt and hook-generated files under $ARTIFACT_DIR"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "checklist layered evidence: missing hook-generated /accounts/:id/models, /v1/models, /v1/chat/completions artifacts"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
|
curl_json() {
|
|
|
|
|
|
local method="$1"
|
|
|
|
|
|
local path="$2"
|
|
|
|
|
|
local payload="${3:-}"
|
|
|
|
|
|
local url="${CRM_BASE_URL%/}$path"
|
|
|
|
|
|
if [[ "$DRY_RUN" == "1" ]]; then
|
|
|
|
|
|
echo "[dry-run] $method $url" >&2
|
|
|
|
|
|
if [[ -n "$payload" ]]; then
|
|
|
|
|
|
printf '%s\n' "$payload" > /dev/stderr
|
|
|
|
|
|
fi
|
|
|
|
|
|
printf '{"dry_run":true,"method":"%s","url":"%s"}\n' "$method" "$url"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -n "$payload" ]]; then
|
|
|
|
|
|
curl -fsS -X "$method" \
|
|
|
|
|
|
-H "Authorization: Bearer $CRM_ADMIN_TOKEN" \
|
|
|
|
|
|
-H 'Content-Type: application/json' \
|
|
|
|
|
|
"$url" \
|
|
|
|
|
|
-d "$payload"
|
|
|
|
|
|
else
|
|
|
|
|
|
curl -fsS -X "$method" \
|
|
|
|
|
|
-H "Authorization: Bearer $CRM_ADMIN_TOKEN" \
|
|
|
|
|
|
"$url"
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
build_host_auth_payload() {
|
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
host_type = os.environ['HOST_AUTH_TYPE']
|
|
|
|
|
|
host_token = os.environ['HOST_AUTH_TOKEN']
|
|
|
|
|
|
print(json.dumps({"type": host_type, "token": host_token}, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
build_host_credentials_payload() {
|
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"host_base_url": os.environ["HOST_BASE_URL"],
|
|
|
|
|
|
"pack_path": os.environ["PACK_PATH"],
|
|
|
|
|
|
"provider_id": os.environ["PROVIDER_ID"],
|
|
|
|
|
|
}
|
|
|
|
|
|
if os.environ.get("HOST_API_KEY"):
|
|
|
|
|
|
payload["host_api_key"] = os.environ["HOST_API_KEY"]
|
|
|
|
|
|
if os.environ.get("HOST_BEARER_TOKEN"):
|
|
|
|
|
|
payload["host_bearer_token"] = os.environ["HOST_BEARER_TOKEN"]
|
|
|
|
|
|
if os.environ.get("ACCESS_API_KEY"):
|
|
|
|
|
|
payload["access_api_key"] = os.environ["ACCESS_API_KEY"]
|
|
|
|
|
|
if os.environ.get("ACCESS_MODE"):
|
|
|
|
|
|
payload["access_mode"] = os.environ["ACCESS_MODE"]
|
|
|
|
|
|
if os.environ.get("MODE"):
|
|
|
|
|
|
payload["mode"] = os.environ["MODE"]
|
|
|
|
|
|
if os.environ.get("SUBSCRIPTION_DAYS"):
|
|
|
|
|
|
payload["subscription_days"] = int(os.environ["SUBSCRIPTION_DAYS"])
|
|
|
|
|
|
if os.environ.get("SUBSCRIPTION_USERS"):
|
|
|
|
|
|
payload["subscription_users"] = [x.strip() for x in os.environ["SUBSCRIPTION_USERS"].split(',') if x.strip()]
|
|
|
|
|
|
if os.environ.get("KEYS"):
|
|
|
|
|
|
payload["keys"] = [x.strip() for x in os.environ["KEYS"].split(',') if x.strip()]
|
|
|
|
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
require_var CRM_BASE_URL
|
|
|
|
|
|
require_var CRM_ADMIN_TOKEN
|
|
|
|
|
|
require_var HOST_NAME
|
|
|
|
|
|
require_var HOST_BASE_URL
|
|
|
|
|
|
require_var PACK_PATH
|
|
|
|
|
|
require_var PROVIDER_ID
|
|
|
|
|
|
|
|
|
|
|
|
MODE="${MODE:-partial}"
|
|
|
|
|
|
ACCESS_MODE="${ACCESS_MODE:-self_service}"
|
|
|
|
|
|
SUBSCRIPTION_DAYS="${SUBSCRIPTION_DAYS:-30}"
|
2026-05-19 13:58:03 +08:00
|
|
|
|
AFTER_IMPORT_HOOK_COMMAND="${AFTER_IMPORT_HOOK_COMMAND:-}"
|
2026-05-18 22:22:22 +08:00
|
|
|
|
|
|
|
|
|
|
if [[ -n "${HOST_BEARER_TOKEN:-}" ]]; then
|
|
|
|
|
|
HOST_AUTH_TYPE="${HOST_AUTH_TYPE:-bearer}"
|
|
|
|
|
|
HOST_AUTH_TOKEN="${HOST_AUTH_TOKEN:-$HOST_BEARER_TOKEN}"
|
|
|
|
|
|
elif [[ -n "${HOST_API_KEY:-}" ]]; then
|
|
|
|
|
|
HOST_AUTH_TYPE="${HOST_AUTH_TYPE:-apikey}"
|
|
|
|
|
|
HOST_AUTH_TOKEN="${HOST_AUTH_TOKEN:-$HOST_API_KEY}"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "missing host credential: set HOST_API_KEY or HOST_BEARER_TOKEN" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
export CRM_BASE_URL CRM_ADMIN_TOKEN HOST_NAME HOST_BASE_URL PACK_PATH PROVIDER_ID
|
|
|
|
|
|
export HOST_AUTH_TYPE HOST_AUTH_TOKEN MODE ACCESS_MODE SUBSCRIPTION_DAYS
|
|
|
|
|
|
export HOST_API_KEY HOST_BEARER_TOKEN ACCESS_API_KEY SUBSCRIPTION_USERS KEYS
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$ARTIFACT_DIR"
|
|
|
|
|
|
echo "artifacts: $ARTIFACT_DIR"
|
2026-05-21 15:45:55 +08:00
|
|
|
|
write_checklist_guide
|
2026-05-18 22:22:22 +08:00
|
|
|
|
|
|
|
|
|
|
HOST_AUTH_JSON="$(build_host_auth_payload)"
|
|
|
|
|
|
export HOST_AUTH_JSON
|
|
|
|
|
|
CREATE_HOST_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
host_auth = json.loads(os.environ['HOST_AUTH_JSON'])
|
|
|
|
|
|
print(json.dumps({
|
|
|
|
|
|
'name': os.environ['HOST_NAME'],
|
|
|
|
|
|
'base_url': os.environ['HOST_BASE_URL'],
|
|
|
|
|
|
'auth': host_auth,
|
|
|
|
|
|
}, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
|
|
|
|
|
|
if RESP_EXISTING_HOST="$(curl_json GET "/api/hosts/$HOST_NAME" 2>/dev/null)"; then
|
2026-05-20 22:09:40 +08:00
|
|
|
|
EXISTING_BASE_URL="$(printf '%s' "$RESP_EXISTING_HOST" | json_get base_url || true)"
|
|
|
|
|
|
if [[ -n "$EXISTING_BASE_URL" && "$EXISTING_BASE_URL" != "$HOST_BASE_URL" ]]; then
|
|
|
|
|
|
echo "existing host $HOST_NAME points to $EXISTING_BASE_URL, expected $HOST_BASE_URL" >&2
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2026-05-18 22:22:22 +08:00
|
|
|
|
fi
|
2026-05-20 22:09:40 +08:00
|
|
|
|
RESP_CREATE_HOST="$(curl_json POST /api/hosts "$CREATE_HOST_PAYLOAD")"
|
2026-05-18 22:22:22 +08:00
|
|
|
|
save_json 01-create-host "$RESP_CREATE_HOST"
|
|
|
|
|
|
HOST_ID="$(printf '%s' "$RESP_CREATE_HOST" | json_get host_id || true)"
|
|
|
|
|
|
HOST_ID="${HOST_ID:-$HOST_NAME}"
|
|
|
|
|
|
|
|
|
|
|
|
echo "host_id=$HOST_ID"
|
|
|
|
|
|
|
|
|
|
|
|
PROBE_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
print(json.dumps({'auth': json.loads(os.environ['HOST_AUTH_JSON'])}, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_PROBE="$(curl_json POST "/api/hosts/$HOST_ID/probe" "$PROBE_PAYLOAD")"
|
|
|
|
|
|
save_json 02-probe-host "$RESP_PROBE"
|
|
|
|
|
|
|
|
|
|
|
|
INSTALL_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
'host_base_url': os.environ['HOST_BASE_URL'],
|
|
|
|
|
|
'pack_path': os.environ['PACK_PATH'],
|
|
|
|
|
|
}
|
|
|
|
|
|
if os.environ.get('HOST_API_KEY'):
|
|
|
|
|
|
payload['host_api_key'] = os.environ['HOST_API_KEY']
|
|
|
|
|
|
if os.environ.get('HOST_BEARER_TOKEN'):
|
|
|
|
|
|
payload['host_bearer_token'] = os.environ['HOST_BEARER_TOKEN']
|
|
|
|
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_INSTALL="$(curl_json POST /api/packs/install "$INSTALL_PAYLOAD")"
|
|
|
|
|
|
save_json 03-install-pack "$RESP_INSTALL"
|
|
|
|
|
|
|
|
|
|
|
|
PREVIEW_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"host_base_url": os.environ["HOST_BASE_URL"],
|
|
|
|
|
|
"pack_path": os.environ["PACK_PATH"],
|
|
|
|
|
|
"provider_id": os.environ["PROVIDER_ID"],
|
|
|
|
|
|
"mode": os.environ.get("MODE", "partial"),
|
|
|
|
|
|
}
|
|
|
|
|
|
if os.environ.get("HOST_API_KEY"):
|
|
|
|
|
|
payload["host_api_key"] = os.environ["HOST_API_KEY"]
|
|
|
|
|
|
if os.environ.get("HOST_BEARER_TOKEN"):
|
|
|
|
|
|
payload["host_bearer_token"] = os.environ["HOST_BEARER_TOKEN"]
|
|
|
|
|
|
if os.environ.get("KEYS"):
|
|
|
|
|
|
payload["keys"] = [x.strip() for x in os.environ["KEYS"].split(',') if x.strip()]
|
|
|
|
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_PREVIEW="$(curl_json POST "/api/providers/$PROVIDER_ID/preview-import" "$PREVIEW_PAYLOAD")"
|
|
|
|
|
|
save_json 04-preview-import "$RESP_PREVIEW"
|
|
|
|
|
|
|
|
|
|
|
|
IMPORT_PAYLOAD="$(build_host_credentials_payload)"
|
|
|
|
|
|
RESP_IMPORT="$(curl_json POST "/api/providers/$PROVIDER_ID/import" "$IMPORT_PAYLOAD")"
|
|
|
|
|
|
save_json 05-import "$RESP_IMPORT"
|
|
|
|
|
|
BATCH_ID="$(printf '%s' "$RESP_IMPORT" | json_get batch_id || true)"
|
|
|
|
|
|
|
2026-05-19 13:58:03 +08:00
|
|
|
|
if [[ -n "$BATCH_ID" && "$DRY_RUN" != "1" ]]; then
|
|
|
|
|
|
RESP_BATCH_DETAIL="$(curl_json GET "/api/import-batches/$BATCH_ID")"
|
|
|
|
|
|
save_json 05a-batch-detail-pre-access "$RESP_BATCH_DETAIL"
|
|
|
|
|
|
export BATCH_DETAIL_FILE="$ARTIFACT_DIR/05a-batch-detail-pre-access.json"
|
|
|
|
|
|
else
|
|
|
|
|
|
unset BATCH_DETAIL_FILE || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -n "$AFTER_IMPORT_HOOK_COMMAND" ]]; then
|
|
|
|
|
|
export BATCH_ID PROVIDER_ID HOST_BASE_URL CRM_BASE_URL ACCESS_MODE MODE ARTIFACT_DIR
|
|
|
|
|
|
bash -lc "$AFTER_IMPORT_HOOK_COMMAND" \
|
|
|
|
|
|
>"$ARTIFACT_DIR/05b-after-import-hook.stdout.txt" \
|
|
|
|
|
|
2>"$ARTIFACT_DIR/05b-after-import-hook.stderr.txt"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
|
echo "batch_id=${BATCH_ID:-unknown}"
|
|
|
|
|
|
|
|
|
|
|
|
ACCESS_PREVIEW_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
'provider_id': os.environ['PROVIDER_ID'],
|
|
|
|
|
|
'mode': os.environ.get('ACCESS_MODE', 'self_service'),
|
|
|
|
|
|
}
|
|
|
|
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_ACCESS_PREVIEW="$(curl_json POST "/api/providers/$PROVIDER_ID/access/preview" "$ACCESS_PREVIEW_PAYLOAD")"
|
|
|
|
|
|
save_json 06-access-preview "$RESP_ACCESS_PREVIEW"
|
|
|
|
|
|
|
|
|
|
|
|
RESP_ACCESS_STATUS="$(curl_json GET "/api/providers/$PROVIDER_ID/access/status")"
|
|
|
|
|
|
save_json 07-access-status "$RESP_ACCESS_STATUS"
|
|
|
|
|
|
|
|
|
|
|
|
RESP_PROVIDER_STATUS="$(curl_json GET "/api/providers/$PROVIDER_ID/status")"
|
|
|
|
|
|
save_json 08-provider-status "$RESP_PROVIDER_STATUS"
|
|
|
|
|
|
|
|
|
|
|
|
RECONCILE_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
payload = {
|
|
|
|
|
|
"host_base_url": os.environ["HOST_BASE_URL"],
|
|
|
|
|
|
"pack_path": os.environ["PACK_PATH"],
|
|
|
|
|
|
"provider_id": os.environ["PROVIDER_ID"],
|
|
|
|
|
|
}
|
|
|
|
|
|
if os.environ.get("HOST_API_KEY"):
|
|
|
|
|
|
payload["host_api_key"] = os.environ["HOST_API_KEY"]
|
|
|
|
|
|
if os.environ.get("HOST_BEARER_TOKEN"):
|
|
|
|
|
|
payload["host_bearer_token"] = os.environ["HOST_BEARER_TOKEN"]
|
|
|
|
|
|
if os.environ.get("ACCESS_API_KEY"):
|
|
|
|
|
|
payload["access_api_key"] = os.environ["ACCESS_API_KEY"]
|
|
|
|
|
|
print(json.dumps(payload, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_RECONCILE="$(curl_json POST "/api/providers/$PROVIDER_ID/reconcile" "$RECONCILE_PAYLOAD")"
|
|
|
|
|
|
save_json 09-reconcile "$RESP_RECONCILE"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -n "$BATCH_ID" && "$DRY_RUN" != "1" ]]; then
|
|
|
|
|
|
RESP_BATCH_DETAIL="$(curl_json GET "/api/import-batches/$BATCH_ID")"
|
|
|
|
|
|
save_json 10-batch-detail "$RESP_BATCH_DETAIL"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ "$SKIP_ROLLBACK" != "1" && -n "$BATCH_ID" ]]; then
|
|
|
|
|
|
ROLLBACK_PAYLOAD="$(python3 - <<'PY'
|
|
|
|
|
|
import json, os
|
|
|
|
|
|
print(json.dumps({'auth': json.loads(os.environ['HOST_AUTH_JSON'])}, ensure_ascii=False))
|
|
|
|
|
|
PY
|
|
|
|
|
|
)"
|
|
|
|
|
|
RESP_ROLLBACK="$(curl_json POST "/api/import-batches/$BATCH_ID/rollback" "$ROLLBACK_PAYLOAD")"
|
|
|
|
|
|
save_json 11-rollback "$RESP_ROLLBACK"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-05-21 15:45:55 +08:00
|
|
|
|
print_artifact_summary
|
2026-05-18 22:22:22 +08:00
|
|
|
|
echo "acceptance flow completed"
|