From 4c271270d4d7fabf1bb2d9094430a539b9c841aa Mon Sep 17 00:00:00 2001 From: Victor Cao Date: Tue, 13 Oct 2026 15:15:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20FRAMEWORK=20vllm=5Fbi150=5Fr2r4=20+=20im?= =?UTF-8?q?age-verify=20CI=20(I8=E2=80=93I12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/build.yml | 88 +++++++++++++++++++++++++++++--- .gitea/workflows/task_info.env | 2 +- Dockerfile | 2 +- README.md | 24 +++++++-- docs/evidence.md | 26 ++++++++-- docs/issues.md | 15 +++++- docs/upload-runbook.md | 23 ++++++--- docs/weekly-report.md | 12 ++--- scripts/apply_patches.py | 5 +- scripts/entrypoint.sh | 2 +- task_info.env.example | 3 +- tests/test_contract.py | 92 +++++++++++++++++++++++++++++++--- 12 files changed, 249 insertions(+), 45 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 1950122..f76408b 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -24,7 +24,7 @@ jobs: echo "GPU_TYPE=${GPU_TYPE:-}" echo "TASK_TYPE=${TASK_TYPE:-}" if [ -z "${FRAMEWORK:-}" ] || [ "${FRAMEWORK}" = "vllm_fix_tokenizer" ]; then - echo "FRAMEWORK 无效。本仓使用 vllm_0_17_0_corex_4_4_0。" + echo "FRAMEWORK 无效。本仓使用 vllm_bi150_r2r4。" exit 1 fi if [ "${GPU_TYPE:-}" != "Iluvatar_bi-150" ]; then @@ -32,8 +32,52 @@ jobs: exit 1 fi IMAGE_NAME="$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')" + IMAGE_TAG="${{ gitea.ref_name }}" + echo "FRAMEWORK=${FRAMEWORK}" >> "$GITEA_ENV" + echo "GPU_TYPE=${GPU_TYPE}" >> "$GITEA_ENV" + echo "TASK_TYPE=${TASK_TYPE:-}" >> "$GITEA_ENV" echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITEA_ENV" - echo "IMAGE_TAG=${{ gitea.ref_name }}" >> "$GITEA_ENV" + echo "IMAGE_TAG=${IMAGE_TAG}" >> "$GITEA_ENV" + if [ -n "${DOCKER_REGISTRY:-}" ] && [ -n "${DOCKER_USERNAME:-}" ]; then + echo "IMAGE=${DOCKER_REGISTRY}/${DOCKER_USERNAME}/${IMAGE_NAME}:${IMAGE_TAG}" >> "$GITEA_ENV" + fi + + # FIXED_TOKEN 由 Runner 注入,不写进仓库,也不打印。 + - name: Validate Image Verify Metadata + run: | + if [ -z "${FIXED_TOKEN:-}" ]; then + echo "FIXED_TOKEN is not configured on runner" + exit 1 + fi + if ! response="$(curl --silent --show-error --location --get \ + 'https://modelhub.org.cn/adminApi/image-verify/validate' \ + --header "Xc-Token: ${FIXED_TOKEN}" \ + --data-urlencode "gpuType=${GPU_TYPE:-}" \ + --data-urlencode "taskType=${TASK_TYPE:-}")"; then + echo "调用 image-verify validate 失败" + exit 1 + fi + VALIDATE_RESPONSE="$response" python3 - <<'PY' + import json + import os + import sys + + raw = os.environ.get("VALIDATE_RESPONSE", "") + try: + body = json.loads(raw) + except json.JSONDecodeError: + print("image-verify validate 返回的不是合法 JSON") + print(raw) + sys.exit(1) + + if body.get("code") == 0 and body.get("data") is True: + print("image-verify 元数据校验通过") + sys.exit(0) + + print(f"image-verify 元数据校验失败: {body.get('message') or '未知错误'}") + print(raw) + sys.exit(1) + PY - name: Build image run: | @@ -45,12 +89,40 @@ jobs: run: | if [ -z "${DOCKER_REGISTRY:-}" ] || [ -z "${DOCKER_USERNAME:-}" ] || [ -z "${DOCKER_PASSWORD:-}" ]; then echo "未配置 DOCKER_REGISTRY、DOCKER_USERNAME 或 DOCKER_PASSWORD。" - echo "本次为 build-only,不登录仓库,不推送镜像。" + echo "本次为 build-only,不登录仓库,不推送镜像,随后也不注册 image-verify。" + echo "PUSHED=0" >> "$GITEA_ENV" exit 0 fi - remote="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/${IMAGE_NAME}:${IMAGE_TAG}" - echo "推送目标: ${remote}" + echo "推送目标: ${IMAGE}" echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" -u "$DOCKER_USERNAME" --password-stdin - docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "$remote" - docker push "$remote" - echo "推送完成: ${remote}" + docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "$IMAGE" + docker push "$IMAGE" + echo "PUSHED=1" >> "$GITEA_ENV" + echo "推送完成: ${IMAGE}" + + # 只有真的推成功才注册,build-only 没有可用的 imageUrl。 + - name: Notify Image Verify + run: | + if [ "${PUSHED:-0}" != "1" ]; then + echo "镜像未推送,跳过 image-verify 注册。" + exit 0 + fi + if [ -z "${FIXED_TOKEN:-}" ]; then + echo "FIXED_TOKEN is not configured on runner" + exit 1 + fi + echo "注册 image-verify: ${IMAGE}" + curl --silent --show-error --fail-with-body --location --request POST \ + 'https://modelhub.org.cn//adminApi/image-verify' \ + --header "Xc-Token: ${FIXED_TOKEN}" \ + --header 'Content-Type: application/json' \ + --data-raw "{ + \"framework\": \"${FRAMEWORK}\", + \"gpuType\": \"${GPU_TYPE}\", + \"imageUrl\": \"${IMAGE}\", + \"taskType\": \"${TASK_TYPE}\", + \"createBy\": \"${{ gitea.actor }}\", + \"repoUrl\": \"${{ gitea.server_url }}/${{ gitea.repository }}\", + \"tag\": \"${{ gitea.ref_name }}\" + }" + echo "image-verify 注册请求已完成。适配下拉是否出现本引擎由人核对。" diff --git a/.gitea/workflows/task_info.env b/.gitea/workflows/task_info.env index 6e5ef66..d74fe75 100644 --- a/.gitea/workflows/task_info.env +++ b/.gitea/workflows/task_info.env @@ -1,3 +1,3 @@ GPU_TYPE=Iluvatar_bi-150 -FRAMEWORK=vllm_0_17_0_corex_4_4_0 +FRAMEWORK=vllm_bi150_r2r4 TASK_TYPE=text-generation diff --git a/Dockerfile b/Dockerfile index 1947173..6b7a172 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE=registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1 FROM ${BASE_IMAGE} -ENV FRAMEWORK=vllm_0_17_0_corex_4_4_0 \ +ENV FRAMEWORK=vllm_bi150_r2r4 \ GPU_TYPE=Iluvatar_bi-150 \ TASK_TYPE=text-generation \ MODEL_DIR=/model \ diff --git a/README.md b/README.md index cb2c714..2041b0f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Track B(竞赛)为 **NO-GO**。没有 `contest/`,没有竞赛构建。 - 没有 ZIP 打包,没有上传驱动的 Makefile 目标。 -- `FRAMEWORK` 使用 `vllm_0_17_0_corex_4_4_0`,不使用 `vllm_fix_tokenizer`。 +- `FRAMEWORK` 使用 `vllm_bi150_r2r4`,不使用 `vllm_fix_tokenizer`。 - tokenizer 修补(I5)未开启。现有 bi150 引擎已覆盖 tokenizer;本仓补的是 chat_template(R2)和 architecture 白名单(R4)。 ## 基线镜像 @@ -40,21 +40,37 @@ make dry-run `make dry-run` 只打印启动计划与模板预览,不启动 vLLM,不访问平台。 -## 改 FRAMEWORK +## FRAMEWORK 默认值写在 [task_info.env.example](task_info.env.example)。镜像内和 CI 使用同一组值: - `GPU_TYPE=Iluvatar_bi-150` -- `FRAMEWORK=vllm_0_17_0_corex_4_4_0` +- `FRAMEWORK=vllm_bi150_r2r4` - `TASK_TYPE=text-generation` +### 为什么不用 `vllm_0_17_0_corex_4_4_0` + +适配页按 FRAMEWORK 聚合。该名字已被 **i-liangzhuobang** 占用展示,本仓 v0.1.0 用它注册时,引擎下拉里看到的是对方的开发者身份。`vllm_bi150_r2r4` 是本仓独有的名字,用来让适配页正确归属。 + 若平台给出新的 FRAMEWORK 名,同时改这三处,并让 `make test` 保持通过: - `task_info.env.example` - `.gitea/workflows/task_info.env` - `Dockerfile` 的 `ENV` -不要改回 `vllm_fix_tokenizer`。 +不要改回 `vllm_fix_tokenizer`,也不要改回撞名的旧值。 + +## CI 与 image-verify + +`v*` 标签触发 [.gitea/workflows/build.yml](.gitea/workflows/build.yml): + +```text +Clone → Load task info → Validate Image Verify Metadata → Build image → Push → Notify Image Verify +``` + +Validate 在构建前校验 `gpuType` 与 `taskType`;Notify 只在镜像真的推送成功后注册 `imageUrl`。两步都要 Runner 注入 `FIXED_TOKEN`,仓库里不存任何 Token。 + +CI 只调用 `adminApi/image-verify` 和它的 `validate`。不代领、也不代提交适配任务。 ## 建仓与选用 diff --git a/docs/evidence.md b/docs/evidence.md index 3c524df..4ae346b 100644 --- a/docs/evidence.md +++ b/docs/evidence.md @@ -2,13 +2,33 @@ 时区:**Asia/Shanghai**。本模板由人工填写。仓库不自动采集审核状态。 -- 镜像 tag:待填写 -- CI 地址:待填写 +## 镜像 + +- 镜像 tag:待填写(例:v0.1.1) +- 完整 imageUrl:待填写(例:harbor.4pd.io/modelhubxc/vc-nosugar/modelhub-engine-bi150-compat:v0.1.1) +- 镜像 digest:待填写 +- CI 地址:待填写(例:https://dev.modelhub.org.cn/VC_noSugar/modelhub-engine-bi150-compat/actions/runs/N) + +## image-verify + +- FRAMEWORK 展示名:vllm_bi150_r2r4 +- Validate 通过时间(Asia/Shanghai):待填写 +- Notify HTTP 结果:待填写 +- createBy / 适配 UI 开发者:应为 VC_noSugar(或页面等价展示) +- 适配下拉是否可见 vllm_bi150_r2r4:待填写 +- 抽检模型(只看下拉可见,不提交任务):Qwen/Qwen2.5-0.5B-Instruct + +## 平台 + +- 平台状态:未推送 - 审核:未开始 - 审核通过时间(Asia/Shanghai):待填写 - 首次被适配任务选用的任务 ID:待填写 - 选用时间(Asia/Shanghai):待填写 -- 平台状态:未推送 - 积分:待平台核实 +## 说明 + +v0.1.0 用的是撞名的 `vllm_0_17_0_corex_4_4_0`,适配页把它聚合到了别人名下,且流水线没有 image-verify 注册。可见性要看含 Validate + Notify 的新 Tag。 + 无卡本地测试只说明 R2/R4 修补逻辑能跑,不说明天垓 150 上的吞吐。 diff --git a/docs/issues.md b/docs/issues.md index 9fefbed..6cbcdba 100644 --- a/docs/issues.md +++ b/docs/issues.md @@ -2,13 +2,24 @@ Track B 不在本仓实现,状态保持 NO-GO。 +## 首版(v0.1.0) + - [x] I0 仓骨架 + README + 非目标声明 - [x] I1 Dockerfile + task_info.env.example - [x] I2 wrapper + dry-run - [x] I3 R2 chat_template 修补 - [x] I4 R4 architecture 白名单 - [ ] I5 tokenizer 修补(未开启;等 R2/R4 确认盖不住目标模型族再做) -- [x] I6 CI build(Tag 触发;无凭据时 build-only;不提交平台任务) +- [x] I6 CI build(Tag 触发;无凭据时 build-only) - [x] I7 人工 runbook + evidence 模板 -本地 `make test` 已覆盖 I3/I4 与 dry-run。镜像是否能在 Runner 上拉到基线 tag,要等第一次 `v*` 流水线。 +## image-verify 注册(待发 v0.1.1) + +- [x] I8 FRAMEWORK 改为 `vllm_bi150_r2r4`,全仓同步,避开与 i-liangzhuobang 撞名 +- [x] I9 CI 在 build 前 Validate Image Verify Metadata,缺 `FIXED_TOKEN` 失败且不打印 Token +- [x] I10 CI 在推送成功后 Notify Image Verify,body 含 framework/gpuType/imageUrl/taskType/createBy/repoUrl/tag +- [x] I11 单测契约翻转:要求 CI 含 image-verify,仍禁 ZIP/contest/代提交,仍拒 `vllm_fix_tokenizer` +- [x] I12 runbook + evidence 写明注册步骤与下拉归属核对 +- [ ] I13 发版:推 main + tag v0.1.1;Actions 出现 Validate + Notify;人工抽检下拉 + +本地 `make test` 20 项覆盖 I3/I4、dry-run 与 CI 契约。镜像能否拉到基线 tag、适配下拉是否归属 `VC_noSugar`,都要等 v0.1.1 流水线和人工核对。 diff --git a/docs/upload-runbook.md b/docs/upload-runbook.md index 6350c38..93199b0 100644 --- a/docs/upload-runbook.md +++ b/docs/upload-runbook.md @@ -1,6 +1,6 @@ # 建仓、打 Tag、选用引擎(人工) -本仓没有自动提交命令。CI 只构建镜像;仓库凭据齐全时才推送。CI 不调用 modelhub.org.cn,不代领适配任务。 +本仓没有自动提交命令。CI 构建镜像、在凭据齐全时推送,并把镜像注册到 `image-verify`。注册只是让适配页能看到这个引擎,不等于代领或代提交适配任务。 积分:待平台核实。 @@ -23,17 +23,24 @@ git push origin v0.1.0 ## 3. 看 CI +流水线顺序:`Clone → Load task info → Validate Image Verify Metadata → Build image → Push → Notify Image Verify`。 + 1. 打开该 Tag 的 Actions / 流水线。 -2. `Load task info` 应打印 `FRAMEWORK=vllm_0_17_0_corex_4_4_0` 和 `GPU_TYPE=Iluvatar_bi-150`。 -3. `Build image` 执行 `docker build`。基线镜像拉取失败时,日志会停在构建步骤;无卡不能代替真机验证。 -4. 若 Runner 没有 `DOCKER_REGISTRY`、`DOCKER_USERNAME`、`DOCKER_PASSWORD`,流水线打印 build-only 并成功结束,不推送。 -5. 三份凭据都由 Runner 环境提供,不写进仓库。有凭据时才会 `docker login` 并推送。 +2. `Load task info` 应打印 `FRAMEWORK=vllm_bi150_r2r4` 和 `GPU_TYPE=Iluvatar_bi-150`。 +3. `Validate Image Verify Metadata` 在构建前 GET `adminApi/image-verify/validate`,校验 `gpuType` 与 `taskType`。缺 `FIXED_TOKEN` 这步直接失败。 +4. `Build image` 执行 `docker build`。基线镜像拉取失败时,日志会停在构建步骤;无卡不能代替真机验证。 +5. `Push` 需要 `DOCKER_REGISTRY`、`DOCKER_USERNAME`、`DOCKER_PASSWORD`。缺任意一项就打印 build-only 并成功结束,不推送。 +6. `Notify Image Verify` 只在推送成功后 POST `adminApi/image-verify`,把 `framework`、`gpuType`、`imageUrl`、`taskType`、`createBy`、`repoUrl`、`tag` 注册上去。build-only 时跳过,因为没有真实 `imageUrl`。 +7. `FIXED_TOKEN` 与 Harbor 密码都由 Runner 注入,不写进仓库,也不在日志里打印。打新 Tag 前先确认旧 Harbor 密码已轮换。 + +旧 Tag 不会自动补注册。`v0.1.0` 没有 Notify 步骤,必须打新 Tag 重跑。 ## 4. 在模盒选用引擎 1. 用浏览器登录模盒。 -2. 等镜像审核通过。审核状态由人在页面上确认。 -3. 在适配任务里选用该引擎。本仓不代为创建或提交任务。 -4. 把审核通过时间、镜像 tag、以及首次被选用的适配任务 ID 写入 `docs/evidence.md`。积分栏保持「待平台核实」。 +2. 在适配任务的引擎下拉里确认能看到 `vllm_bi150_r2r4`,且开发者 / `createBy` 是 `VC_noSugar`(或页面等价展示)。如果看到的是别人的名字,说明 FRAMEWORK 又撞名了。 +3. 等镜像审核通过。审核状态由人在页面上确认。 +4. 在适配任务里选用该引擎。本仓不代为创建或提交任务。可用 `Qwen/Qwen2.5-0.5B-Instruct`(`Qwen2ForCausalLM`,在 R4 白名单内)只做下拉可见性抽检。 +5. 把审核通过时间、镜像 tag、`imageUrl`、以及首次被选用的适配任务 ID 写入 `docs/evidence.md`。积分栏保持「待平台核实」。 字段以登录后的控制台为准。 diff --git a/docs/weekly-report.md b/docs/weekly-report.md index 5c3e24e..e93b9c5 100644 --- a/docs/weekly-report.md +++ b/docs/weekly-report.md @@ -1,7 +1,7 @@ -【引擎 bi150-compat】2026-10-13 -- 本周:I0–I4、I6、I7 完成;I5 未开启。阻塞:尚未推送 Gitea,也还没有真机流水线。 -- 镜像 tag:未构建。默认基线 registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1(公开仓反推) -- 本地 test:见 make test -- 平台:未推送 +【引擎 bi150-compat · image-verify】2026-10-13 +- 本周:I8–I12 补注册 CI;FRAMEWORK 改为 vllm_bi150_r2r4(避开与 i-liangzhuobang 撞名) +- 镜像:v0.1.0 已推;待发 v0.1.1(含 validate + notify) +- 本地 test:make test 20 项通过 +- 平台:Harbor 有镜像但适配下拉尚无本仓身份;根因缺 image-verify 注册 - 积分:待核实 -- 下周:人工建仓、打 v* Tag、看 CI;审核通过或被选用后再填 docs/evidence.md +- 下周:打 v0.1.1 → 确认下拉可见 vllm_bi150_r2r4 且归属 VC_noSugar → 证据写入 docs/evidence.md(不代提交适配) diff --git a/scripts/apply_patches.py b/scripts/apply_patches.py index 3648c13..206eb08 100755 --- a/scripts/apply_patches.py +++ b/scripts/apply_patches.py @@ -12,6 +12,7 @@ import sys from pathlib import Path FORBIDDEN_FRAMEWORK = "vllm_fix_tokenizer" +DEFAULT_FRAMEWORK = "vllm_bi150_r2r4" TOKENIZER_PATCH_KEY = "modelhub_tokenizer_patch" @@ -26,7 +27,7 @@ def engine_root() -> Path: def assert_framework(framework: str) -> None: if not framework or framework == FORBIDDEN_FRAMEWORK: raise PatchError( - "FRAMEWORK 必须是 vllm_0_17_0_corex_4_4_0," + f"FRAMEWORK 必须是 {DEFAULT_FRAMEWORK}," "不能使用 vllm_fix_tokenizer" ) @@ -157,7 +158,7 @@ def main(argv: list[str] | None = None) -> int: root = engine_root() parser = argparse.ArgumentParser(description="应用 R2/R4 模型目录修补") parser.add_argument("--model-dir", required=True, type=Path) - parser.add_argument("--framework", default="vllm_0_17_0_corex_4_4_0") + parser.add_argument("--framework", default=DEFAULT_FRAMEWORK) parser.add_argument("--unknown-policy", default="fail", choices=("fail", "skip")) parser.add_argument("--force-chat-template", action="store_true") parser.add_argument("--tokenizer-patch", action="store_true") diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 65ca81f..aab8d8b 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -61,7 +61,7 @@ APPLY_TOKENIZER_PATCH="${APPLY_TOKENIZER_PATCH:-0}" FORCE_CHAT_TEMPLATE="${FORCE_CHAT_TEMPLATE:-0}" if [[ -z "$FRAMEWORK" || "$FRAMEWORK" == "vllm_fix_tokenizer" ]]; then - echo "拒绝 FRAMEWORK=${FRAMEWORK:-空}。本仓使用 vllm_0_17_0_corex_4_4_0。" >&2 + echo "拒绝 FRAMEWORK=${FRAMEWORK:-空}。本仓使用 vllm_bi150_r2r4。" >&2 exit 1 fi if [[ -z "$GPU_TYPE" ]]; then diff --git a/task_info.env.example b/task_info.env.example index f24f848..f3287fa 100644 --- a/task_info.env.example +++ b/task_info.env.example @@ -1,6 +1,7 @@ # 本地与镜像内的默认任务信息。平台若给出不同 FRAMEWORK,只改这一处,并保持 Dockerfile 的 ENV 与 CI 文件一致。 +# FRAMEWORK 必须是本仓独有的名字,否则适配页会把镜像聚合到同名的别人名下。 GPU_TYPE=Iluvatar_bi-150 -FRAMEWORK=vllm_0_17_0_corex_4_4_0 +FRAMEWORK=vllm_bi150_r2r4 TASK_TYPE=text-generation MODEL_DIR=/model SERVED_MODEL_NAME=llm diff --git a/tests/test_contract.py b/tests/test_contract.py index 98e9c2f..726f3c9 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -1,9 +1,12 @@ import os +import re import subprocess import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] +FRAMEWORK = "vllm_bi150_r2r4" +RETIRED_FRAMEWORK = "vllm_0_17_0_corex_4_4_0" class EngineContractTest(unittest.TestCase): @@ -11,11 +14,11 @@ class EngineContractTest(unittest.TestCase): example = (ROOT / "task_info.env.example").read_text(encoding="utf-8") workflow_env = (ROOT / ".gitea/workflows/task_info.env").read_text(encoding="utf-8") dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8") - self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", example) + self.assertIn(f"FRAMEWORK={FRAMEWORK}", example) self.assertIn("GPU_TYPE=Iluvatar_bi-150", example) - self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", workflow_env) + self.assertIn(f"FRAMEWORK={FRAMEWORK}", workflow_env) self.assertIn("GPU_TYPE=Iluvatar_bi-150", workflow_env) - self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", dockerfile) + self.assertIn(f"FRAMEWORK={FRAMEWORK}", dockerfile) self.assertIn("GPU_TYPE=Iluvatar_bi-150", dockerfile) self.assertIn( "registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1", @@ -24,13 +27,83 @@ class EngineContractTest(unittest.TestCase): self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", example) self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", workflow_env) self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", dockerfile) + self.assertNotIn(RETIRED_FRAMEWORK, example) + self.assertNotIn(RETIRED_FRAMEWORK, workflow_env) + self.assertNotIn(RETIRED_FRAMEWORK, dockerfile) - def test_ci_does_not_submit_platform_task(self) -> None: + def test_ci_registers_image_verify(self) -> None: workflow = (ROOT / ".gitea/workflows/build.yml").read_text(encoding="utf-8") - self.assertNotIn("adminApi", workflow) - self.assertNotIn("modelhub.org.cn", workflow) + self.assertIn("Validate Image Verify Metadata", workflow) + self.assertIn("https://modelhub.org.cn/adminApi/image-verify/validate", workflow) + self.assertIn("Notify Image Verify", workflow) + self.assertIn("https://modelhub.org.cn//adminApi/image-verify", workflow) + self.assertIn("Xc-Token: ${FIXED_TOKEN}", workflow) + self.assertIn("FIXED_TOKEN is not configured on runner", workflow) + for field in ("framework", "gpuType", "imageUrl", "taskType", "createBy", "repoUrl", "tag"): + self.assertIn(f'\\"{field}\\"', workflow) self.assertIn("build-only", workflow) - self.assertIn('tags:', workflow) + self.assertIn("tags:", workflow) + self.assertIn(f"本仓使用 {FRAMEWORK}", workflow) + self.assertNotIn(RETIRED_FRAMEWORK, workflow) + + def test_ci_notifies_only_after_push(self) -> None: + workflow = (ROOT / ".gitea/workflows/build.yml").read_text(encoding="utf-8") + self.assertIn('PUSHED=1', workflow) + self.assertIn('if [ "${PUSHED:-0}" != "1" ]; then', workflow) + notify_at = workflow.index("Notify Image Verify") + push_at = workflow.index("Push when registry credentials exist") + validate_at = workflow.index("Validate Image Verify Metadata") + build_at = workflow.index("Build image") + self.assertLess(validate_at, build_at) + self.assertLess(build_at, push_at) + self.assertLess(push_at, notify_at) + + def test_ci_does_not_submit_adaptation_task(self) -> None: + workflow = (ROOT / ".gitea/workflows/build.yml").read_text(encoding="utf-8") + allowed = { + "https://modelhub.org.cn/adminApi/image-verify/validate", + "https://modelhub.org.cn//adminApi/image-verify", + } + for url in re.findall(r"https?://[^\s'\"]+", workflow): + if "modelhub.org.cn" not in url: + continue + self.assertIn(url, allowed, f"CI 只允许调用 image-verify,发现 {url}") + + def test_no_secret_literals_in_repo(self) -> None: + tracked = subprocess.run( + ["git", "ls-files"], + cwd=ROOT, + text=True, + capture_output=True, + check=True, + ).stdout.split() + pattern = re.compile(r"(FIXED_TOKEN|DOCKER_PASSWORD)\s*=\s*\S") + for name in tracked: + path = ROOT / name + if not path.is_file(): + continue + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + for line in text.splitlines(): + self.assertIsNone(pattern.search(line), f"{name} 可能写入了明文密钥: {line}") + + def test_ci_does_not_log_secrets(self) -> None: + workflow = (ROOT / ".gitea/workflows/build.yml").read_text(encoding="utf-8") + for line in workflow.splitlines(): + stripped = line.strip() + if "FIXED_TOKEN" in stripped: + self.assertFalse( + stripped.startswith("echo") and "${FIXED_TOKEN" in stripped, + f"不要把 FIXED_TOKEN 打进日志: {stripped}", + ) + if stripped.startswith("echo") and "$DOCKER_PASSWORD" in stripped: + self.assertIn( + "docker login", + stripped, + f"DOCKER_PASSWORD 只能管道给 docker login: {stripped}", + ) def test_no_zip_or_contest_targets(self) -> None: makefile = (ROOT / "Makefile").read_text(encoding="utf-8") @@ -53,11 +126,12 @@ class EngineContractTest(unittest.TestCase): check=False, ) self.assertEqual(completed.returncode, 0, completed.stderr) - self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", completed.stdout) + self.assertIn(f"FRAMEWORK={FRAMEWORK}", completed.stdout) self.assertIn("GPU_TYPE=Iluvatar_bi-150", completed.stdout) self.assertIn("python3 -m vllm.entrypoints.openai.api_server", completed.stdout) self.assertIn("dry-run: 不启动进程,不访问平台", completed.stdout) self.assertNotIn("modelhub.org.cn", completed.stdout) + self.assertNotIn(RETIRED_FRAMEWORK, completed.stdout) self.assertIn("<|user|>", completed.stdout) def test_dry_run_rejects_forbidden_framework(self) -> None: @@ -73,6 +147,7 @@ class EngineContractTest(unittest.TestCase): ) self.assertNotEqual(completed.returncode, 0) self.assertIn("vllm_fix_tokenizer", completed.stderr) + self.assertIn(FRAMEWORK, completed.stderr) def test_tokenizer_patch_stays_off(self) -> None: import shutil @@ -80,6 +155,7 @@ class EngineContractTest(unittest.TestCase): import apply_patches + self.assertEqual(apply_patches.DEFAULT_FRAMEWORK, FRAMEWORK) with tempfile.TemporaryDirectory() as tmp: model = Path(tmp) / "model" shutil.copytree(ROOT / "tests" / "fixtures" / "fake-model", model)