From 860da3c387b6b617f09705695ed8759bf6695e36 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 29 May 2026 17:38:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B6=E5=8F=A3=20v0.1=20=E8=A7=84?= =?UTF-8?q?=E6=A0=BC=E7=BC=BA=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + AGENTS.md | 4 +- deploy/deploy.json | 8 ++-- docs/reference/spec-v01-agentrun-mgr.md | 8 ++-- docs/reference/spec-v01-agentrun-runner.md | 10 +++-- docs/reference/spec-v01-backend-adapter.md | 6 +-- docs/reference/spec-v01-backend-codex.md | 8 ++-- docs/reference/spec-v01-cicd.md | 10 ++--- docs/reference/spec-v01-cli.md | 42 +++++++++++-------- docs/reference/spec-v01-postgres.md | 8 ++-- .../reference/spec-v01-secret-distribution.md | 10 ++--- docs/reference/spec-v01-services.md | 8 ++-- docs/reference/spec-v01-validation.md | 23 +++++++--- scripts/agentrun | 20 +++++++++ src/mgr/kubernetes-runner-job.ts | 9 ++-- src/runner/k8s-job.ts | 14 ++++--- src/selftest/cases/20-runner-k8s-job.ts | 8 +++- 17 files changed, 127 insertions(+), 70 deletions(-) create mode 100755 scripts/agentrun diff --git a/.gitignore b/.gitignore index 5bb85b7..5c03650 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .state/ +.worktree/ logs/ node_modules/ package-lock.json diff --git a/AGENTS.md b/AGENTS.md index f7293dc..b60902b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,12 +36,12 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓 ## Critical CLI Spec Rule -- P0: AgentRun CLI 和服务开发必须遵循 UniDesk `cli-spec` 原则:默认 JSON 输出、禁止空输出伪成功、禁止长阻塞 CLI、日志可见、配置显式校验、稳定跨服务边界优先使用 RESTful API。 +- P0: AgentRun CLI 和服务开发必须遵循 UniDesk `cli-spec` 原则:默认 JSON 输出、禁止空输出伪成功、禁止长阻塞 CLI、日志可见、配置显式校验、稳定跨服务边界优先使用 RESTful API。G14 非交互 route 优先使用 `./scripts/agentrun ...` launcher;它只负责定位 Bun 并转入 `scripts/agentrun-cli.ts`。 - P0: 一旦新增 CLI,入口文件必须保持轻量,具体实现拆入 `scripts/src/`;长任务必须快速返回,并提供 status/log/event 轮询。 ## Critical v0.1 Implementation Stack Rule -- P0: AgentRun `v0.1` 自研 runtime、CLI、manager、runner、backend adapter、Codex backend 和后续 scheduler 的优先实现语言是 Bun + TypeScript;官方 CLI 入口是 `bun scripts/agentrun-cli.ts`,复杂逻辑拆入 `scripts/src/` 和 `src/`。 +- P0: AgentRun `v0.1` 自研 runtime、CLI、manager、runner、backend adapter、Codex backend 和后续 scheduler 的优先实现语言是 Bun + TypeScript;官方 TypeScript CLI 入口是 `scripts/agentrun-cli.ts`,G14/CI/人工非交互命令使用 `./scripts/agentrun` 启动同一入口,复杂逻辑拆入 `scripts/src/` 和 `src/`。 - P0: `backendProfile=codex` 必须通过 Codex CLI app-server stdio 协议执行,启动受控 `codex app-server --listen stdio://`,使用 JSON-RPC 方法 `initialize`、`thread/start` 或 `thread/resume`、`turn/start`;不得在 `v0.1` 把 Codex backend 实现为直接 Responses HTTP 代理、文本 fallback 或 fake provider。 - P0: 实现 Codex backend 前必须参考 UniDesk Code Queue 的 `src/components/microservices/code-queue/src/code-agent/codex.ts`、`common.ts`,以及 HWLAB 的 `internal/cloud/codex-stdio-session.mjs`、`scripts/code-agent-chat-smoke.mjs`;复用协议、redaction、trace、failure 分类和 Secret projection 经验,不复制环境专用路径或明文密钥。 diff --git a/deploy/deploy.json b/deploy/deploy.json index b5fbd2e..21a52d0 100644 --- a/deploy/deploy.json +++ b/deploy/deploy.json @@ -31,10 +31,12 @@ "serviceAccount": "agentrun-v01-runner", "secretMounts": [ { - "name": "codex-home", + "name": "codex-secret-projection", "secretRef": { "name": "agentrun-v01-provider-codex", "keys": ["auth.json", "config.toml"] }, - "mountPath": "/home/agentrun/.codex", - "readOnly": true + "projectionPath": "/var/run/agentrun/secrets/codex-0", + "runtimeCopyPath": "/home/agentrun/.codex", + "readOnly": true, + "writableCopy": true } ], "env": [ diff --git a/docs/reference/spec-v01-agentrun-mgr.md b/docs/reference/spec-v01-agentrun-mgr.md index 0dfcfa0..068d335 100644 --- a/docs/reference/spec-v01-agentrun-mgr.md +++ b/docs/reference/spec-v01-agentrun-mgr.md @@ -108,7 +108,7 @@ POST /api/v1/commands/:commandId/ack | 规格项 | 状态 | 说明 | | --- | --- | --- | | `agentrun-mgr` 服务规格 | 已定义 | 本文为 v0.1 manager 权威。 | -| Manager REST API | 已实现骨架 | 已有 run、command、event、backends、runner register、claim、lease heartbeat、poll、ack、status 和 health/readiness 的 HTTP JSON 骨架;仍需后续真实部署验收。 | -| Tenant policy boundary | 已定义/未实现 | v0.1 只做最小 schema/allowlist/secretScope 边界。 | -| Postgres durable adapter | 已实现骨架 | live runtime 通过 `DATABASE_URL` 使用 Postgres durable store;memory store 仅用于显式 self-test/dev。见 [spec-v01-postgres.md](spec-v01-postgres.md)。 | -| Observability 最小合同 | 已实现骨架 | events append-only、terminal status、failureKind、health/readiness store 状态、runner claim/lease/backend events 和 Secret/DSN redaction 已进入 manager 骨架;集中 trace 和部署级观测仍属后续工作。 | +| Manager REST API | 已实现/已通过主闭环 | 已有 run、command、event、backends、runner register、claim、lease heartbeat、poll、ack、status、runner Job 创建和 health/readiness 的 HTTP JSON API;真实 runtime 已通过 RESTful API 主闭环。 | +| Tenant policy boundary | 已实现最小边界 | v0.1 已做 schema、tenant/backend allowlist、executionPolicy 和 secretScope 结构校验;业务授权仍由 UniDesk/HWLAB 自己判定。 | +| Postgres durable adapter | 已实现/已通过主闭环 | live runtime 通过 `DATABASE_URL` 使用 Postgres durable store;memory store 仅用于显式 self-test/dev。见 [spec-v01-postgres.md](spec-v01-postgres.md)。 | +| Observability 最小合同 | 已实现主路径 | events append-only、terminal status、failureKind、health/readiness store 状态、runner claim/lease/backend events 和 Secret/DSN redaction 已进入 manager;集中 trace 和部署级观测仍属后续工作。 | diff --git a/docs/reference/spec-v01-agentrun-runner.md b/docs/reference/spec-v01-agentrun-runner.md index df826c9..b566ad4 100644 --- a/docs/reference/spec-v01-agentrun-runner.md +++ b/docs/reference/spec-v01-agentrun-runner.md @@ -29,6 +29,8 @@ Runner Secret 只能通过 Kubernetes Secret projection、ServiceAccount/RBAC Kubernetes Job runner 必须把 credential source 与 runtime home 分开:Secret volume 只读挂在 `/var/run/agentrun/secrets/...`,`/home/agentrun` 由 `emptyDir` 提供可写空间,`CODEX_HOME` 指向 `/home/agentrun/.codex`,`AGENTRUN_CODEX_SECRET_HOME` 指向只读 projection。runner/backend 在启动 provider 前只复制授权文件,不打印内容。 +Kubernetes Job runner 必须设置有限保留时间。`v0.1` 默认 `ttlSecondsAfterFinished=86400`,用于保留最近完成 Job 的调试窗口,同时避免长期堆积 `Completed` runner Job 污染运行面观察。该 TTL 是 Job manifest 的运行面属性,不是 CI/CD 门禁;需要延长保留时间时必须通过受控 Job render/input 显式覆盖,并在 issue 或 PR 中说明原因。 + ## Runner 生命周期 标准状态方向: @@ -102,7 +104,7 @@ Runner 日志必须实时 flush 到文件或 pod log,CLI 启动 runner 时必 | 规格项 | 状态 | 说明 | | --- | --- | --- | | `agentrun-runner` 服务规格 | 已定义 | 本文为 v0.1 runner 权威。 | -| Kubernetes Job runner | 部分实现 | 已提供 `runner job --dry-run` Job manifest 渲染骨架;正式 `runner job` 通过 manager REST 创建 Kubernetes Job,固定使用 `agentrun-v01-runner` ServiceAccount、manager URL、runId/commandId/attemptId、executionPolicy、SecretRef 文件投影和 writable Codex runtime home;真实集群综合联调仍需验收。 | -| host process runner | 部分实现 | `runner start` 和 `src/runner/main.ts` 进入同一套 `runOnce`,可通过 manager register/claim/poll/report 执行自测试。 | -| claim/lease/report client | 部分实现 | 已拆出 runner manager API client,覆盖 register、claim、lease heartbeat、poll command、ack、append event 和 terminal status;durable store 仍待 Postgres adapter 接入。 | -| runner redaction | 已定义/未实现 | 需与 backend adapter 共同实现。 | +| Kubernetes Job runner | 已实现/已通过主闭环 | `runner job` 通过 manager REST 创建 Kubernetes Job,固定使用 `agentrun-v01-runner` ServiceAccount、manager URL、runId/commandId/attemptId、executionPolicy、SecretRef 文件投影、writable Codex runtime home 和有限 TTL;真实 `agentrun-v01` runner Job 已完成 Codex turn。 | +| host process runner | 已实现 | `runner start` 和 `src/runner/main.ts` 进入同一套 `runOnce`,可通过 manager register/claim/poll/report 执行自测试。 | +| claim/lease/report client | 已实现 | 已拆出 runner manager API client,覆盖 register、claim、lease heartbeat、poll command、ack、append event 和 terminal status;live runtime 通过 manager 写入 Postgres durable store。 | +| runner redaction | 已实现主路径 | runner/backend event 和 Job 输出使用 redaction;复杂审计仍按 [spec-v01-validation.md](spec-v01-validation.md) 的人工验收抽查。 | diff --git a/docs/reference/spec-v01-backend-adapter.md b/docs/reference/spec-v01-backend-adapter.md index 670cf58..604d5f1 100644 --- a/docs/reference/spec-v01-backend-adapter.md +++ b/docs/reference/spec-v01-backend-adapter.md @@ -86,7 +86,7 @@ Adapter 必须把 backend 错误映射为稳定 failureKind: | 规格项 | 状态 | 说明 | | --- | --- | --- | | Backend adapter 合同 | 已定义 | 本文为 v0.1 adapter 权威。 | -| 通用 adapter 模块 | 未实现 | 需要后续代码实现。 | -| event normalization | 已定义/未实现 | 需后续实现和测试。 | -| failure mapping | 已定义/未实现 | 需后续实现和测试。 | +| 通用 adapter 模块 | 已实现最小形态 | `src/backend/adapter.ts` 作为 runner 进程内 adapter 入口,`v0.1` 只路由真实 `codex` profile;多 backend 路由仍 deferred。 | +| event normalization | 已实现主路径 | Codex backend 已把 backend_status、assistant_message、tool_call、command_output、error 和 terminal_status 归一化为 manager events;复杂事件审计按人工验收抽查。 | +| failure mapping | 已实现主路径 | Codex backend 已覆盖 missing secret、auth/rate/availability、protocol、JSON parse、invalid response、spawn、timeout 和 cancel 分类;真实负向场景按 [spec-v01-validation.md](spec-v01-validation.md) T7 手动验收。 | | 多 backend 路由 | Deferred | v0.1 只要求一个真实 backend 闭环。 | diff --git a/docs/reference/spec-v01-backend-codex.md b/docs/reference/spec-v01-backend-codex.md index 962c215..ba0b7bc 100644 --- a/docs/reference/spec-v01-backend-codex.md +++ b/docs/reference/spec-v01-backend-codex.md @@ -99,8 +99,8 @@ Run 的 `executionPolicy.secretScope` 应引用 `agentrun-v01-provider-codex` | 规格项 | 状态 | 说明 | | --- | --- | --- | | Codex backend 规格 | 已定义 | 本文为 v0.1 第一真实 backend 权威。 | -| Codex Secret projection | 未实现 | 需要后续 Kubernetes Secret 和 runner/backend manifest。 | -| Codex adapter | 已部分实现 | 当前代码已实现受控 `codex app-server --listen stdio://`、`initialize`/`thread/start`/`thread/resume`/`turn/start` response 校验、stderr 有界诊断、spawn/JSON parse/response invalid/timeout/provider 5xx availability failureKind,以及包含 retry error notification 的 fake app-server 自测试。 | -| 错误可观测与脱敏 | 已部分实现 | child env、cwd、workspace 和 Codex home 只输出摘要;stderr tail 有界且标记截断;事件和 failure 统一走 redaction。 | -| 真实 provider turn | 未实现 | 综合联调必须真实完成后才能发布通过。 | +| Codex Secret projection | 已实现/已通过主闭环 | runner Job 使用只读 Secret projection 和 writable `CODEX_HOME`,Codex 测试凭据来自 `agentrun-v01-provider-codex` 的 `auth.json`/`config.toml`。 | +| Codex adapter | 已实现/已通过主闭环 | 当前代码已实现受控 `codex app-server --listen stdio://`、`initialize`/`thread/start`/`thread/resume`/`turn/start` response 校验、stderr 有界诊断、spawn/JSON parse/response invalid/timeout/provider 5xx availability failureKind,以及包含 retry error notification 的 fake app-server 自测试。 | +| 错误可观测与脱敏 | 已实现主路径 | child env、cwd、workspace 和 Codex home 只输出摘要;stderr tail 有界且标记截断;事件和 failure 统一走 redaction。 | +| 真实 provider turn | 已通过主闭环 | 真实 Codex provider turn 已经通过 RESTful API 和 CLI 综合联调;每次发布仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 | | hostPath `~/.codex` | 不采用 | 只能通过 Kubernetes Secret projection 注入。 | diff --git a/docs/reference/spec-v01-cicd.md b/docs/reference/spec-v01-cicd.md index b0a4430..13fbb05 100644 --- a/docs/reference/spec-v01-cicd.md +++ b/docs/reference/spec-v01-cicd.md @@ -144,7 +144,7 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st ## 验收标准 -- `G14:/root/agentrun-v01` source worktree 为 `v0.1...origin/v0.1` 且 clean。 +- `G14:/root/agentrun-v01` source worktree 为 `v0.1...origin/v0.1` 且 clean;`/root/agentrun-v01/.worktree/` 是受控并行 worktree 根,必须被 `.gitignore` 忽略,不得污染固定 source worktree 状态。 - `AGENTS.md` 和 `docs/reference/` 不得把 `agentrun_dev` 或 `agentrun_prod` 写成 `v0.1` 当前 namespace、Argo destination、Pipeline target 或验收目标;只允许在废弃说明和历史背景中提及。 - `agentrun-v01` namespace 存在,且 `agentrun_dev`/`agentrun_prod` 不参与 `v0.1` 发布判定。 - `v0.1-gitops` branch 和 `deploy/gitops/g14/runtime-v01` 成为 Argo desired state 来源。 @@ -158,8 +158,8 @@ Tekton promotion 可以读取 `deploy/deploy.json` 来 render runtime desired st | 规格项 | 状态 | 说明 | | --- | --- | --- | | `v0.1` source branch | 已建立 | `origin/v0.1` 存在。 | -| `G14:/root/agentrun-v01` source worktree | 已建立 | 固定 source worktree 使用 `v0.1` 分支。 | -| `agentrun-v01` namespace | 部分实现 | GitOps rendered manifest 已声明 namespace;运行面初始化和 Argo 同步仍需综合联调验收。 | -| `v0.1-gitops` branch | 部分实现 | Tekton promotion 已生成 artifact catalog 与 runtime desired state;后续每个 source commit 仍需由 PipelineRun 推进。 | -| 纯 Tekton/Argo lane | 部分实现 | 已有 `agentrun-ci` Pipeline、BuildKit 镜像发布、GitOps promotion 模板和 Argo Application 模板;真实 runtime sync 与综合联调仍需收口。 | +| `G14:/root/agentrun-v01` source worktree | 已建立/待保持 clean | 固定 source worktree 使用 `v0.1` 分支;`.worktree/` 必须忽略,确保并行 worktree 不让固定 source worktree 变 dirty。 | +| `agentrun-v01` namespace | 已实现/已通过主闭环 | GitOps lane 已同步 manager、Postgres、ServiceAccount、SecretRef 和 runner Job 所需对象;发布前仍按 [spec-v01-validation.md](spec-v01-validation.md) 手动复验。 | +| `v0.1-gitops` branch | 已实现 | Tekton promotion 生成 artifact catalog 与 runtime desired state,Argo 从 `deploy/gitops/g14/runtime-v01` 同步。 | +| 纯 Tekton/Argo lane | 已实现/已通过主闭环 | `agentrun-ci` Pipeline、BuildKit 镜像发布、GitOps promotion 和 Argo Application 已形成闭环;不得回退到自定义 runner 或 dev/prod。 | | `dev/prod` 废弃口径 | 已定义 | 本文明确 `agentrun_dev` 和 `agentrun_prod` 不作为当前规格目标。 | diff --git a/docs/reference/spec-v01-cli.md b/docs/reference/spec-v01-cli.md index 7d29828..e570e56 100644 --- a/docs/reference/spec-v01-cli.md +++ b/docs/reference/spec-v01-cli.md @@ -19,22 +19,30 @@ scripts/agentrun-cli.ts scripts/src/*.ts ``` -CLI 官方运行方式固定为 Bun + TypeScript:`bun scripts/agentrun-cli.ts ...`。`node`、`tsx` 或 shell wrapper 只能作为本地开发辅助,不能成为 `v0.1` 用户文档、CI/CD 或综合联调的权威入口。入口文件只负责参数解析和路由,具体实现拆到 `scripts/src/`。CLI 命令默认输出 JSON;任何命令无 stdout 都是失败。单次 CLI 调用必须在 60 秒内返回,长时间模型 turn 通过 status/events 后续命令观察。 +CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交互 SSH route、CI task 和人工验收命令中,推荐使用仓库内 launcher: + +```bash +./scripts/agentrun ... +``` + +该 launcher 只负责定位 `bun`(优先 `BUN_BIN`、其次 PATH、`$HOME/.bun/bin/bun` 和 G14 默认 `/root/.bun/bin/bun`),然后转入同一个 `scripts/agentrun-cli.ts`。它不实现第二套路由、不读取额外配置、不绕过 TypeScript CLI。`bun scripts/agentrun-cli.ts ...` 仍可在 PATH 已包含 Bun 的交互 shell 中使用;`node`、`tsx` 或其他 wrapper 只能作为本地开发辅助,不能成为 `v0.1` 综合联调的权威入口。 + +入口文件只负责参数解析和路由,具体实现拆到 `scripts/src/`。CLI 命令默认输出 JSON;任何命令无 stdout 都是失败。单次 CLI 调用必须在 60 秒内返回,长时间模型 turn 通过 status/events 后续命令观察。 ## 初始命令族 ```bash -bun scripts/agentrun-cli.ts runs create --json-file -bun scripts/agentrun-cli.ts runs show -bun scripts/agentrun-cli.ts runs events --after-seq --limit -bun scripts/agentrun-cli.ts commands create --type turn --json-file -bun scripts/agentrun-cli.ts commands show -bun scripts/agentrun-cli.ts runner start --run-id --backend -bun scripts/agentrun-cli.ts runner job --run-id --command-id -bun scripts/agentrun-cli.ts runner job --dry-run --run-id --command-id --image -bun scripts/agentrun-cli.ts secrets codex render --dry-run [--codex-home ] -bun scripts/agentrun-cli.ts backends list -bun scripts/agentrun-cli.ts server start|status|stop|logs +./scripts/agentrun runs create --json-file +./scripts/agentrun runs show +./scripts/agentrun runs events --after-seq --limit +./scripts/agentrun commands create --type turn --json-file +./scripts/agentrun commands show --run-id +./scripts/agentrun runner start --run-id --backend +./scripts/agentrun runner job --run-id --command-id +./scripts/agentrun runner job --dry-run --run-id --command-id --image +./scripts/agentrun secrets codex render --dry-run [--codex-home ] +./scripts/agentrun backends list +./scripts/agentrun server start|status ``` 具体参数可以在实现时按代码结构微调,但行为必须保持: @@ -57,7 +65,7 @@ bun scripts/agentrun-cli.ts server start|status|stop|logs ### T1 CLI run 生命周期 -阅读 `AGENTS.md`、本文和 [spec-v01-validation.md](spec-v01-validation.md),然后用正式 AgentRun CLI 创建 run、提交 `turn` command、启动 runner、轮询 command 和 events 直到 terminal_status。确认每个命令输出非空 JSON,60 秒内返回,并给出下一步可执行命令或 logPath。 +阅读 `AGENTS.md`、本文和 [spec-v01-validation.md](spec-v01-validation.md),然后用 `./scripts/agentrun` 创建 run、提交 `turn` command、启动 runner、轮询 command 和 events 直到 terminal_status。确认每个命令输出非空 JSON,60 秒内返回,并给出下一步可执行命令或 logPath。若直接执行 `bun scripts/agentrun-cli.ts`,必须先证明当前 shell 的 PATH 能找到 Bun;G14 route 默认以 `./scripts/agentrun` 为准。 ### T2 CLI 错误可见性 @@ -76,7 +84,7 @@ bun scripts/agentrun-cli.ts server start|status|stop|logs | 规格项 | 状态 | 说明 | | --- | --- | --- | | AgentRun CLI 规格 | 已定义 | 本文为 v0.1 CLI 权威。 | -| `scripts/agentrun-cli.ts` | 部分实现 | 已提供 run/command/event/backend/server 基础命令和 JSON envelope。 | -| CLI 调 manager REST | 部分实现 | CLI 通过 `ManagerClient` 调 manager REST;当前自测试使用内存 manager。 | -| runner start/job | 部分实现 | `runner start` 可执行 host process runner;`runner job --dry-run` 可渲染 Kubernetes Job JSON;`runner job` 正式路径通过 manager REST 创建 Kubernetes Job 并快速返回 job identity、SecretRef 和轮询命令。 | -| CLI 测试规格 | 已定义 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md)。 | +| `scripts/agentrun-cli.ts` | 已实现 | 已提供 run/command/event/backend/server 基础命令和 JSON envelope;`scripts/agentrun` 是同一入口的 Bun 定位 launcher。 | +| CLI 调 manager REST | 已实现 | CLI 通过 `ManagerClient` 调 manager REST;自测试可用内存 manager,综合联调必须指向真实 `agentrun-v01` manager。 | +| runner start/job | 已实现 | `runner start` 可执行 host process runner;`runner job --dry-run` 可渲染 Kubernetes Job JSON;`runner job` 正式路径通过 manager REST 创建 Kubernetes Job 并快速返回 job identity、SecretRef、retention 和轮询命令。 | +| CLI 测试规格 | 已定义/已验证主闭环 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md);每次发布仍按手动交互验收复跑。 | diff --git a/docs/reference/spec-v01-postgres.md b/docs/reference/spec-v01-postgres.md index 9c7fdca..223a072 100644 --- a/docs/reference/spec-v01-postgres.md +++ b/docs/reference/spec-v01-postgres.md @@ -73,8 +73,8 @@ Secret 名称和 key 可以在实现时按 Kubernetes 命名限制微调,但 | 规格项 | 状态 | 说明 | | --- | --- | --- | | Postgres durable store 规格 | 已定义 | 本文为 v0.1 存储权威。 | -| StatefulSet/Service/PVC | 未实现 | 需要后续 GitOps lane 初始化。 | -| migration ledger | 已实现骨架 | `agentrun-mgr` 启动 Postgres adapter 时幂等创建 `agentrun_schema_migrations` 并记录 migration id/checksum;live DB 迁移验收仍依赖后续 GitOps lane 初始化。 | -| manager Postgres adapter | 已实现骨架 | `agentrun-mgr` 通过 `DATABASE_URL` 启用 Postgres adapter,持久化 runs、commands、events、runners、backends 和 leases;缺少 `DATABASE_URL` 时 live runtime fail fast,memory 只允许显式 self-test/dev。 | -| health/readiness store 状态 | 已实现骨架 | health/readiness 返回 adapter、reachable、migrationReady、migrationId、failureKind 和 redacted Secret 状态,不输出 DSN 明文。 | +| StatefulSet/Service/PVC | 已实现/已通过主闭环 | `agentrun-v01-postgres` StatefulSet、Service 和 PVC 已由 GitOps runtime 提供,作为 `agentrun-v01` durable store。 | +| migration ledger | 已实现/已通过主闭环 | `agentrun-mgr` 启动 Postgres adapter 时幂等创建 `agentrun_schema_migrations` 并记录 migration id/checksum;readiness 必须显示 migration ready。 | +| manager Postgres adapter | 已实现/已通过主闭环 | `agentrun-mgr` 通过 `DATABASE_URL` 启用 Postgres adapter,持久化 runs、commands、events、runners、backends 和 leases;缺少 `DATABASE_URL` 时 live runtime fail fast,memory 只允许显式 self-test/dev。 | +| health/readiness store 状态 | 已实现 | health/readiness 返回 adapter、reachable、migrationReady、migrationId、failureKind 和 redacted Secret 状态,不输出 DSN 明文。 | | file/sqlite durable store | 不采用 | 只可用于临时本地测试,不作为 v0.1 runtime truth。 | diff --git a/docs/reference/spec-v01-secret-distribution.md b/docs/reference/spec-v01-secret-distribution.md index 5286bb3..0b327ed 100644 --- a/docs/reference/spec-v01-secret-distribution.md +++ b/docs/reference/spec-v01-secret-distribution.md @@ -114,7 +114,7 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声 `v0.1` 提供只读 CLI 工具,用 operator 本地 `~/.codex/auth.json` 与 `~/.codex/config.toml` 构造 Kubernetes Secret 创建计划: ```bash -bun scripts/agentrun-cli.ts secrets codex render --dry-run +./scripts/agentrun secrets codex render --dry-run ``` 可选参数: @@ -154,8 +154,8 @@ bun scripts/agentrun-cli.ts secrets codex render --dry-run | 规格项 | 状态 | 说明 | | --- | --- | --- | | Secret 分发规格 | 已定义 | 本文为 v0.1 provider credential 分发权威。 | -| Kubernetes SecretRef 注入 | 部分实现 | runner Job dry-run 和正式 Job 创建路径已按 run `executionPolicy.secretScope.providerCredentials` 生成 Secret volume projection、writable runtime home 和 `AGENTRUN_CODEX_SECRET_HOME`;真实 Secret 与 Codex turn 仍需综合联调验收。 | -| Codex Secret dry-run 工具 | 已实现 | `bun scripts/agentrun-cli.ts secrets codex render --dry-run` 只输出 Secret 创建计划、hash 和 redacted manifest 摘要,不执行 apply。 | -| Codex auth/config file projection | 部分实现 | backend readiness 检查 `auth.json`/`config.toml` 可读性,缺失时返回 `secret-unavailable`;self-test 使用临时文件模拟投影。 | -| redaction 最小规则 | 部分实现 | Secret dry-run 工具、event、Job dry-run 输出和 self-test 已验证不打印测试 token;复杂审计仍待后续补齐。 | +| Kubernetes SecretRef 注入 | 已实现/已通过主闭环 | runner Job dry-run 和正式 Job 创建路径已按 run `executionPolicy.secretScope.providerCredentials` 生成 Secret volume projection、writable runtime home 和 `AGENTRUN_CODEX_SECRET_HOME`;真实 Secret 与 Codex turn 已通过主闭环。 | +| Codex Secret dry-run 工具 | 已实现 | `./scripts/agentrun secrets codex render --dry-run` 只输出 Secret 创建计划、hash 和 redacted manifest 摘要,不执行 apply。 | +| Codex auth/config file projection | 已实现主路径 | backend readiness 检查 `auth.json`/`config.toml` 可读性,缺失时返回 `secret-unavailable`;真实 runner Job 将只读 projection 复制到 writable `CODEX_HOME`。 | +| redaction 最小规则 | 已实现主路径 | Secret dry-run 工具、event、Job dry-run 输出、self-test 和真实主闭环均不打印 Secret value;复杂审计按 [spec-v01-validation.md](spec-v01-validation.md) 人工抽查。 | | 外部 secret manager | 未采用 | 如需 Vault/ExternalSecrets/SOPS,后续单独更新规格。 | diff --git a/docs/reference/spec-v01-services.md b/docs/reference/spec-v01-services.md index e66aea3..5095457 100644 --- a/docs/reference/spec-v01-services.md +++ b/docs/reference/spec-v01-services.md @@ -16,7 +16,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Code Agent 执行基础设施。` ## 语言与协议选型 -AgentRun `v0.1` 的自研组件优先使用 Bun + TypeScript 实现:`agentrun-mgr`、`agentrun-runner`、backend adapter、Codex backend、AgentRun CLI 和后续 scheduler 都属于该边界。官方 CLI 入口固定为 `bun scripts/agentrun-cli.ts`,入口只做参数解析和路由,复杂逻辑拆到 `scripts/src/` 与 `src/`。Postgres、Kubernetes、Tekton、Argo CD、YAML manifest 和 shell 级容器启动命令属于外部运行面或部署面,不受“必须 TypeScript 实现”的约束。 +AgentRun `v0.1` 的自研组件优先使用 Bun + TypeScript 实现:`agentrun-mgr`、`agentrun-runner`、backend adapter、Codex backend、AgentRun CLI 和后续 scheduler 都属于该边界。官方 TypeScript CLI 入口固定为 `scripts/agentrun-cli.ts`,入口只做参数解析和路由,复杂逻辑拆到 `scripts/src/` 与 `src/`;G14/CI/人工非交互命令使用 `./scripts/agentrun` launcher 启动同一入口。Postgres、Kubernetes、Tekton、Argo CD、YAML manifest 和 shell 级容器启动命令属于外部运行面或部署面,不受“必须 TypeScript 实现”的约束。 `backendProfile=codex` 的 `v0.1` 协议固定为 Codex CLI app-server JSON-RPC over stdio:runner/backend adapter 启动受控 `codex app-server --listen stdio://` 子进程,经 stdin/stdout 发送换行分隔 JSON-RPC,请求顺序至少覆盖 `initialize`、`thread/start` 或 `thread/resume`、`turn/start`。直接调用 Responses HTTP、OpenAI SDK、`codex exec` 一次性输出或文本 fallback 只能作为诊断/自测试辅助,不能作为 Codex backend 综合联调通过证据。 @@ -167,7 +167,7 @@ Manager 负责校验、保存和返回这些字段;runner 只能消费已保 | Codex backend 规格 | 已定义 | 见 [spec-v01-backend-codex.md](spec-v01-backend-codex.md)。 | | AgentRun CLI 规格 | 已定义 | 见 [spec-v01-cli.md](spec-v01-cli.md)。 | | Scheduler deferred 规格 | 已定义 | 见 [spec-v01-scheduler.md](spec-v01-scheduler.md)。 | -| `agentrun-mgr` 实现 | 已实现骨架 | 已有 REST API、Postgres durable store 选择、migration ledger、runner claim/lease/report、health/readiness 和 self-test memory 模式骨架;仍需 G14 `agentrun-v01` 真实 Postgres/GitOps 验收。 | -| `agentrun-runner` 实现 | 已实现骨架 | 已有 host process runner 与 Kubernetes Job dry-run 渲染骨架,runner 通过 manager API claim/poll/report,不直连 Postgres;真实 Kubernetes Job 联调仍需验收。 | -| 第一真实 backend | 已实现骨架 | Codex app-server stdio backend 已有协议、失败分类、脱敏和 fake self-test;真实 Codex provider turn 仍需综合联调验收。 | +| `agentrun-mgr` 实现 | 已实现/已通过主闭环 | 已有 REST API、Postgres durable store、migration ledger、runner claim/lease/report、health/readiness 和 self-test memory 模式;真实 `agentrun-v01` runtime 已通过 Postgres/GitOps/readiness 和 run lifecycle 主闭环。 | +| `agentrun-runner` 实现 | 已实现/已通过主闭环 | host process runner 与 Kubernetes Job 共用 `runOnce`,runner 通过 manager API claim/poll/report,不直连 Postgres;真实 Kubernetes Job 已完成 Codex turn。 | +| 第一真实 backend | 已实现/已通过主闭环 | Codex app-server stdio backend 已有协议、失败分类、脱敏和 fake self-test;真实 Codex provider turn 已通过 RESTful API 与 CLI 主闭环。 | | 自动 scheduler | Deferred | 不作为 `v0.1` 第一阶段验收目标。 | diff --git a/docs/reference/spec-v01-validation.md b/docs/reference/spec-v01-validation.md index ef21399..b5c1371 100644 --- a/docs/reference/spec-v01-validation.md +++ b/docs/reference/spec-v01-validation.md @@ -63,7 +63,7 @@ CLI 交互联调验证正式操作入口是否能支撑人工使用和自动脚 CLI 交互联调必须满足: -- 使用 `bun scripts/agentrun-cli.ts` 完成 create run、create command、start runner、show command 和 events polling。 +- 使用 `./scripts/agentrun` 完成 create run、create command、start runner、show command 和 events polling;直接使用 `bun scripts/agentrun-cli.ts` 前必须证明当前 shell 的 PATH 能找到 Bun。 - 每个 CLI 调用默认输出 JSON,stdout 不为空,且 60 秒内返回;长时间模型 turn 必须通过后续 status/events 命令观察。 - 创建类命令必须返回 `runId`、`commandId`、status、job/process identity、logPath 和下一步 poll command;查询类命令必须返回当前 state、terminal_status、failureKind 和 event cursor。 - CLI 输出、日志摘要和错误信息必须保留可观测性,不能只返回成功/失败布尔值;错误必须能区分 missing SecretRef、provider auth failure、runner lease conflict、backend failure 和 infra failure。 @@ -120,7 +120,7 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察 ### T4 CLI 交互联调 -阅读 `AGENTS.md`、本文和 [cli.md](cli.md),然后用 AgentRun CLI 手动测试以下内容:在真实 `agentrun-v01` 运行面创建 run,提交一个 `turn` command,启动或触发真实 runner,并轮询 command status 与 run events 直到出现 terminal_status。确认每个步骤只使用正式 CLI 命令,不使用 debug 子命令或 mock/fake backend;每次调用 60 秒内返回非空 JSON,包含 runId、commandId、status、job/process identity、logPath 或下一步 poll command;日志、event 和 CLI 输出不得出现 provider credential、DSN password、token 或 URL credential 明文。最后重启或滚动 `agentrun-mgr`,再用 CLI 查询同一个 run,确认 command state、events 和 terminal_status 仍可见。 +阅读 `AGENTS.md`、本文和 [spec-v01-cli.md](spec-v01-cli.md),然后用 `./scripts/agentrun` 手动测试以下内容:在真实 `agentrun-v01` 运行面创建 run,提交一个 `turn` command,启动或触发真实 runner,并轮询 command status 与 run events 直到出现 terminal_status。确认每个步骤只使用正式 CLI 命令,不使用 debug 子命令或 mock/fake backend;每次调用 60 秒内返回非空 JSON,包含 runId、commandId、status、job/process identity、logPath 或下一步 poll command;日志、event 和 CLI 输出不得出现 provider credential、DSN password、token 或 URL credential 明文。最后重启或滚动 `agentrun-mgr`,再用 CLI 查询同一个 run,确认 command state、events 和 terminal_status 仍可见。 ### T5 RESTful API 交互联调 @@ -128,15 +128,28 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察 ### T6 CLI 与 RESTful 观察一致性 -阅读本文、[cli.md](cli.md) 和 [spec-v01-services.md](spec-v01-services.md),然后对同一个真实 run 分别用 CLI 和 RESTful API 查询 run、command、events 和 terminal_status。确认 CLI 只是正式 API 的受控操作入口,不维护一套独立状态;两种交互面看到的 run id、command id、state、terminal_status、failureKind、event seq 和 redaction 结果必须一致。 +阅读本文、[spec-v01-cli.md](spec-v01-cli.md) 和 [spec-v01-services.md](spec-v01-services.md),然后对同一个真实 run 分别用 CLI 和 RESTful API 查询 run、command、events 和 terminal_status。确认 CLI 只是正式 API 的受控操作入口,不维护一套独立状态;两种交互面看到的 run id、command id、state、terminal_status、failureKind、event seq 和 redaction 结果必须一致。 + +### T7 手动负向交互验收 + +阅读本文和各组件 spec,然后在真实 `agentrun-v01` 运行面用正式 CLI 或 RESTful API 手动验证以下负向场景。该项是人工验收标准,不是自动化脚本、CI 门禁或固定 gate: + +- 使用非法 `tenantId`、非法 `backendProfile` 或缺失必填 run 字段创建 run,确认返回 JSON,包含 `failureKind` 和 `message`。 +- 对同一 run 使用相同 idempotency key 提交相同 command 两次,再用相同 key 提交不同 payload,确认前两次返回同一个 command,第三次结构化失败。 +- 对同一 pending run 启动两个真实 runner 或重复 claim,确认只有一个 owner 成功,失败方为 `runner-lease-conflict` 或等价 failureKind,且不继续调用 backend。 +- 用不存在的 provider SecretRef 创建 run 并启动 runner,确认失败为 `secret-unavailable`,不会降级为 mock pass,也不会打印 Secret value。 +- 对同一 run 分页读取 events,确认 `seq` 单调、`afterSeq` 翻页无重复、重复读取同一页不会改变 durable facts。 + +T7 只定义人工验收的检查面和判定口径。若后续为减少人工操作引入 helper 命令,它只能输出手动步骤、当前证据或 dry-run 计划,不能把这些负向场景改造成阻断发布的自动门禁。 ## 规格的实现情况 | 规格项 | 状态 | 说明 | | --- | --- | --- | | 两层验证模型 | 已定义 | 本文为 v0.1 验证权威。 | -| 自测试 task | 未实现 | 需要后续随组件实现补齐。 | -| 综合联调 task/runbook | 未实现 | 需要 runtime、Postgres、SecretRef 和真实 backend 就绪后补齐。 | +| 自测试 task | 已实现 | `src/selftest/run.ts` 自动发现 `src/selftest/cases/*.ts`;覆盖 redaction/Postgres contract、manager memory、runner Job render/create、Codex fake app-server stdio 和 Secret render。 | +| 综合联调验收规格 | 已增强 | 本文保留人工交互验收模型;T4-T7 定义 CLI、RESTful、一致性和负向场景的手动验收标准,不新增自动脚本或门禁。 | | CLI 交互联调标准 | 已定义 | 必须只使用正式 CLI,验证真实 run 生命周期和可观测输出。 | | RESTful API 交互联调标准 | 已定义 | 必须直连真实 manager HTTP JSON API,验证服务合同和 durable facts。 | +| 真实主闭环 | 已通过 | 当前 v0.1 已通过真实 Tekton/Argo、Postgres、SecretRef、Kubernetes runner Job、Codex stdio turn、RESTful API 和 CLI 主闭环;每次发布仍需按本文手动复验。 | | mock 作为发布证据 | 不采用 | mock 只能证明自测试通过。 | diff --git a/scripts/agentrun b/scripts/agentrun new file mode 100755 index 0000000..cd3a11c --- /dev/null +++ b/scripts/agentrun @@ -0,0 +1,20 @@ +#!/usr/bin/env sh +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +ROOT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) + +if [ -n "${BUN_BIN:-}" ] && [ -x "${BUN_BIN:-}" ]; then + BUN="$BUN_BIN" +elif command -v bun >/dev/null 2>&1; then + BUN="$(command -v bun)" +elif [ -n "${HOME:-}" ] && [ -x "${HOME:-}/.bun/bin/bun" ]; then + BUN="${HOME:-}/.bun/bin/bun" +elif [ -x "/root/.bun/bin/bun" ]; then + BUN="/root/.bun/bin/bun" +else + printf '%s\n' '{"ok":false,"failureKind":"infra-failed","message":"bun runtime not found; set BUN_BIN or install Bun"}' + exit 127 +fi + +exec "$BUN" "$ROOT_DIR/scripts/agentrun-cli.ts" "$@" diff --git a/src/mgr/kubernetes-runner-job.ts b/src/mgr/kubernetes-runner-job.ts index dd08187..a398bd9 100644 --- a/src/mgr/kubernetes-runner-job.ts +++ b/src/mgr/kubernetes-runner-job.ts @@ -74,10 +74,13 @@ export async function createKubernetesRunnerJob(options: { store: AgentRunStore; logPath: `kubectl -n ${render.namespace} logs job/${render.jobName}`, }, secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })), + retention: { + ttlSecondsAfterFinished: render.ttlSecondsAfterFinished, + }, pollCommands: { - run: `bun scripts/agentrun-cli.ts runs show ${run.id} --manager-url ${managerUrl}`, - command: `bun scripts/agentrun-cli.ts commands show ${commandId} --run-id ${run.id} --manager-url ${managerUrl}`, - events: `bun scripts/agentrun-cli.ts runs events ${run.id} --manager-url ${managerUrl} --after-seq 0 --limit 100`, + run: `./scripts/agentrun runs show ${run.id} --manager-url ${managerUrl}`, + command: `./scripts/agentrun commands show ${commandId} --run-id ${run.id} --manager-url ${managerUrl}`, + events: `./scripts/agentrun runs events ${run.id} --manager-url ${managerUrl} --after-seq 0 --limit 100`, }, warnings: render.warnings, kubernetes: { diff --git a/src/runner/k8s-job.ts b/src/runner/k8s-job.ts index 3147f66..d3eeec9 100644 --- a/src/runner/k8s-job.ts +++ b/src/runner/k8s-job.ts @@ -47,21 +47,25 @@ export function renderRunnerJobDryRun(options: RunnerJobRenderOptions): JsonReco sourceCommit: render.sourceCommit, }, secretRefs: render.secretRefs.map((item) => ({ profile: item.profile, name: item.secretRef.name, namespace: item.secretRef.namespace ?? render.namespace, keys: item.secretRef.keys ?? [], mountPath: item.runtimeMountPath, projectionPath: item.projectionMountPath, writableCopy: true, valuesPrinted: false })), + retention: { + ttlSecondsAfterFinished: render.ttlSecondsAfterFinished, + }, pollCommands: { - run: `bun scripts/agentrun-cli.ts runs show ${options.run.id} --manager-url ${options.managerUrl}`, - events: `bun scripts/agentrun-cli.ts runs events ${options.run.id} --manager-url ${options.managerUrl} --after-seq 0 --limit 100`, + run: `./scripts/agentrun runs show ${options.run.id} --manager-url ${options.managerUrl}`, + events: `./scripts/agentrun runs events ${options.run.id} --manager-url ${options.managerUrl} --after-seq 0 --limit 100`, }, warnings: render.warnings, manifest: render.manifest, }; } -export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; warnings: string[] } { +export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { manifest: JsonRecord; namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; serviceAccountName: string; secretRefs: CredentialProjection[]; warnings: string[]; ttlSecondsAfterFinished: number } { const namespace = options.namespace ?? "agentrun-v01"; const attemptId = options.attemptId ?? `attempt_${Date.now().toString(36)}`; const runnerId = options.runnerId ?? `runner_${shortHash(`${options.run.id}:${attemptId}:${options.commandId}`)}`; const sourceCommit = options.sourceCommit ?? process.env.AGENTRUN_SOURCE_COMMIT ?? "unknown"; const serviceAccountName = options.serviceAccountName ?? "agentrun-v01-runner"; + const ttlSecondsAfterFinished = options.ttlSecondsAfterFinished ?? 86_400; const jobName = `agentrun-v01-runner-${shortDnsHash(options.run.id, attemptId)}`; const secretRefs = credentialProjections(options.run, namespace); const warnings: string[] = []; @@ -82,7 +86,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani }, spec: { backoffLimit: options.backoffLimit ?? 0, - ttlSecondsAfterFinished: options.ttlSecondsAfterFinished ?? 86_400, + ttlSecondsAfterFinished, template: { metadata: { labels: labels(options.run, jobName), @@ -122,7 +126,7 @@ export function renderRunnerJobManifest(options: RunnerJobRenderOptions): { mani }, }, }; - return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, warnings }; + return { manifest, namespace, jobName, runnerId, attemptId, sourceCommit, serviceAccountName, secretRefs, warnings, ttlSecondsAfterFinished }; } function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string; jobName: string; runnerId: string; attemptId: string; sourceCommit: string; secretRefs: CredentialProjection[] }): JsonRecord[] { diff --git a/src/selftest/cases/20-runner-k8s-job.ts b/src/selftest/cases/20-runner-k8s-job.ts index 12298ed..7c6a4f9 100644 --- a/src/selftest/cases/20-runner-k8s-job.ts +++ b/src/selftest/cases/20-runner-k8s-job.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { chmod, mkdir, writeFile } from "node:fs/promises"; +import { chmod, mkdir, readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { startManagerServer } from "../../mgr/server.js"; import { MemoryAgentRunStore } from "../../mgr/store.js"; @@ -23,6 +23,7 @@ const selfTest: SelfTestCase = async (context) => { }); assert.equal(rendered.dryRun, true); assert.equal(rendered.mutation, false); + assert.equal(((rendered.retention as JsonRecord).ttlSecondsAfterFinished), 86_400); assert.equal((rendered.jobIdentity as { serviceAccountName?: string }).serviceAccountName, "agentrun-v01-runner"); assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome); assertNoSecretLeak(rendered); @@ -56,11 +57,14 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin const jobItem = await createRunWithCommand(jobClient, context, "job create smoke", "selftest-job-create", 15_000); const created = await jobClient.post(`/api/v1/runs/${jobItem.runId}/runner-jobs`, { commandId: jobItem.commandId, attemptId: "attempt_selftest_create" }); assert.equal((created as { mutation?: unknown }).mutation, true); + assert.equal(((created as JsonRecord).retention as JsonRecord).ttlSecondsAfterFinished, 86_400); + const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord; + assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400); assertNoSecretLeak(created); } finally { await new Promise((resolve) => serverWithKubectl.server.close(() => resolve())); } - return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-create-api"] }; + return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl"] }; } finally { await new Promise((resolve) => server.server.close(() => resolve())); }