> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clousight.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 架构

> 共享生命周期、插件契约与证据模型。

Clousight Bench 对云产品的**运行时工程能力**进行基准测试，而非模型智能。
其核心思想是：不同云产品的负载差异悬殊，但流水线完全相同。

## 生命周期（所有领域共用）

12 个阶段，**4 个相位**。相位是读懂这台机器的方式，阶段是它下面的失败归因细节。

```
(1) 准备 PREPARE   RESOLVE -> VALIDATE -> DESCRIBE ... PREFLIGHT ... DESCRIBE
                   还没碰云、没花钱——请求不合法就停在这里
(2) 连接 CONNECT   SETUP ............................ TEARDOWN (finally)
                   唯一与云对话、唯一会花钱的窗口
(3) 测量 MEASURE   EXECUTE -> SEAL
                   只产生观测，不产生结论
    ───────── 这条线之后，云已彻底离场 ─────────
(4) 定论 CONCLUDE  SCORE -> ENRICH -> PERSIST -> optional PUBLISH
                   纯函数、可离线重算；enricher 只能追加，不能推翻
```

MEASURE 与 CONCLUDE 之间那条线就是整个信任故事，而且它由函数签名而非约定来保证：
`task.score()` 只接受 `ObservationBundle`，adapter 和 target 在类型上就够不到判定。
凡是云能影响的，都发生在这条线以上。

各阶段依次为：

* **RESOLVE** — 根据 `RunSpec` 查找 DomainPack、任务和适配器。
  不写入 `run.stages`：RESOLVE 失败根本不会产生记录，所以"RESOLVE: ok"在每条记录里都是常量。
* **VALIDATE** — 解析并校验 RunSpec、目标、参数和任务配置（`core/validation.py`）。RESOLVE 和 VALIDATE 失败均属 `UserInputError`：CLI 退出码为 2，**不写入任何记录**。
* **DESCRIBE** — 依据插件的声明（`workload_identity`、`provenance`、`environment_facts`）组装本次运行的 identity、environment 与三个指纹。它**横跨 preflight 闸门**：需要活适配器才能取到的 facts 要等 PREFLIGHT 通过之后才收集，因此 DESCRIBE 只在最后记录一次。插件在这里崩溃会得到 `invalid` 记录，且不会制备任何资源。（0.6.0 之前这些失败挂在 VALIDATE 名下，导致记录出现"VALIDATE failed、PREFLIGHT ok"这种读不通的组合。）
* **PREFLIGHT** — `adapter.preflight(task)`：在制备之前检查凭证、SDK、连通性以及本次基准测试在该云上所需的最低权限。CRITICAL 级失败会生成一条 `invalid` 记录，且永远不会进入 SETUP。可通过 `--skip-preflight` 跳过；可使用 `csbench doctor` 单独运行。
* **SETUP** — `adapter.setup()`：通过 Terraform 制备资源,或通过 SDK/HTTP 建立连接。`setup()` 与 `teardown()` 都是**可选的 no-op 钩子**——"创建 → 接入 → 销毁"三段可分离,**纯接入**运行(经 `target.endpoint` / `credentials_ref` 配置接入一个已运行的服务)只用"接入"这一段。一次运行是否制备是显式的适配器能力 `provisions_resources()`(默认:真实云的 live 运行;`*-endpoint` 配置接入适配器与模拟器为 `False`,或继承 `ProvisionedCloudAdapter` 恒为 `True`)。**制备云机器——live 确认门、成本预算/账本、资源回收(reaper)——仅在 `provisions_resources()` 为 `True` 时运行**;纯接入运行全部跳过。
* **EXECUTE** — `task.execute(adapter, params)`：驱动负载，返回仅含原始证据的 `ObservationBundle`。
* **SEAL** — `core/observation.py::seal()`：验证 bundle 格式完整且可进行规范化编码。它**不从云上取任何东西**——bundle 是 EXECUTE 已经返回的；这里是打分前的封存。它自成一个阶段，是为了让"套件跑崩了"和"套件返回了编不出来的东西"成为两种可归因的失败。0.6.0 之前叫 `COLLECT`，旧名仍保留在 `STAGES` 中，因此 0.5.x 写下的记录照样能读。
* **SCORE** — `task.score(bundle)`：纯函数，生成测量值和发现。评分器失败时保留观测值，并记录一条 SCORE 错误。
* **ENRICH** — 已安装的各 `ResultEnricher` 相互隔离运行：失败仅记录为 ENRICH 阶段错误，不会改变 `status`。
* **PERSIST** — `core/store.py`：写临时文件、刷盘、`fsync`、原子重命名；当结果目录不可写时，会将紧急转储写入系统临时目录（路径会被打印出来）。
* **PUBLISH** — 默认关闭，需注入 `ResultPublisher` 才生效。在 PERSIST 之后运行，写入仅追加的回执；永远不会改写核心记录。

**TEARDOWN 不在上述流程步骤之列。** 它是围绕 SETUP → SEAL 的强制性 `finally` 边界：一旦进入 SETUP，`adapter.teardown()` 必然执行，包括 `setup()` 中途失败的情况；teardown 失败会被记录为独立的阶段错误，不会覆盖导致失败的 execute 或 collect 错误。

**PREPARE 与 CONNECT 之间还有两道闸门，它们不是独立阶段**——live 运行确认（`--allow-live`）与成本预算（`--cost-budget`）。两者都在**制备任何资源之前**拒绝，表现形式也相同：`SETUP: skipped`、状态 `invalid`，外加一条 `critical` 发现，其 `details.remediation` 写明该怎么做。`csbench run` 会把这一行打到 stderr，因此被拦下的运行不必去读 stdout 上的 JSON 就能自我解释。

## 结果契约（schema 0.4）

顶层字段固定为：`schema_version`、`run`、`identity`、`environment`、`fingerprints`、`measurements`、`findings`、`observations`、`series`、`artifacts`、`items`、`extensions`、`errors`、`status`。

* `items`（schema 0.4，可选）是**逐项基座**：一组 `ItemResult`（`item_id`、`group`、`input`/`output`/`reference`、逐项 `scores` 带 4 态 `status` ∈ `ok`·`fail`·`skip`·`error`、`usage`）。标量 `measurements` 是它们的聚合（`core/aggregate.py`——mean/ratio/geomean/百分位 + Wilson/正态置信区间 + 按 `group` 切片）。尚未迁移到逐项打分的套件为空。由 `CSBENCH_MAX_PERSISTED_ITEMS`(默认 1000)封顶;截断记录在 `extensions.core.items_meta`,绝不静默。
* `status` ∈ `completed` · `failed` · `invalid` · `unsupported`——这是 `execute()` 会返回的全部取值。还有第五个值 `interrupted`，只属于 Ctrl-C：teardown 照常执行、记录照常落盘，随后 `KeyboardInterrupt` 被重新抛出，因此这个状态只会出现在磁盘上，不会作为返回值出现。不存在布尔型 `ok`、顶层 `metrics`、顶层 `evidence_layer`，也没有 `config_hash`。
* `environment.execution` ∈ `simulated` · `live` · `unknown`，标记数据来自模拟运行时还是真实云端。该字段被折叠进环境指纹，因此模拟结果与真实结果永远不会混入同一分析池。
* 每条测量值携带 `value`、`unit` 和 `official`，可选字段为 `reproducibility_class`、`aggregation`、`sample_count`、`notes` 和 `ci`。
* **判定型指标**(`reproducibility_class="judge-based"`)通过判定基座(`core/judge.py`)落地:`JudgeModel`(配置接入的 `EndpointJudge`,带 SSRF 校验)被要求给出**分类**判定,再由固定算术映射为分数——刻意拒绝 G-Eval 不可复现的 top-logprob 加权。`judge_emit` 从任意判定模型取得结构化输出(原生 JSON-schema 模式,否则 prompt + 花括号切片/尾逗号修复)。`response-quality` 指标是参考实现:带可选自一致性的 rubric 判定;未配置判定模型时干净地 `skip`(因此 CI 从不调用真实判定模型),并在每条逐项分数中记录判定模型 id 与判定结果。通过"把指标 opt-in + 配置接入一个判定模型"来运行:`params: {extra_metrics: [response_quality], judge: {provider: openai-compatible, endpoint: ..., model: ..., credentials_ref: env:KEY, cache: .judge_cache.json}}`——配置了判定模型的运行会在打分阶段做网络 I/O,按设计不可离线重打分。config 里给 `cache` 路径会用内容寻址的 `CachingJudge` 包裹判定模型,重跑复用判定、不再重复付费 LLM 调用(只缓存判定打分,绝不缓存 environmental 数值)。
* **Golden ↔ 已执行** 拆分:benchmark spec = 版本化的 `DatasetHandle`(`version` + `digest`,在库固定)+ 每条 item 的 `reference`(golden);运行的 `output` 是已执行侧。没有单独的 Golden 子系统——逐项基座已同时承载两者。
* 每条发现携带稳定的 `code`、`severity`、`summary` 和 `details`。
* 每条阶段错误携带 `stage`、`code`、`type`、`message` 和 `retryable`。堆栈跟踪永远不存入记录；`csbench run --debug` 会将其写入 `<results>/debug/<run_id>.log`。
* 所有摘要均为对规范 JSON 编码的完整 SHA-256（`core/canonical.py`）：UTF-8、键排序、无多余空白，NaN/Infinity 被拒绝。
* `extensions["core"]` 为核心保留；插件在自己的命名空间下写入（例如 `extensions["cb-pricing"]`）。

Schema `1.0` 记录不再受支持。请重新运行基准测试以生成当前的 `0.4` 记录。

## 运行计划与统计（Phase 1C）

单一数字不构成测量。`csbench run --repeat N --warmup W`（`ops/runplan.py`）将同一 `RunSpec` 经由上述生命周期运行 `warmup + repeat` 次——每次运行仍为独立的带摘要 `0.4` 记录——随后：

* 丢弃 `warmup` 次运行（冷启动 / JIT / 缓存效应不代表你要发布的稳态），并在每条记录的 `extensions["core"]["run_plan"]` 下标注其角色，使选择可审计、永远不成为指纹；
* 将正式运行的结果每个测量项汇聚为一个分布（`core/statistics.py`）：数值型 → `n / mean / stdev / min / max / p50 / p95 / cv`，标签型 → 分布 / `mode` / `agreement`；
* 将 `run_plan_aggregate`（含独立 SHA-256 摘要）写入 `results/aggregates/…`，报告加载器将其识别为记录汇总而跳过。

**可比性经过校验，而非假定。** 只有 `benchmark` 和 `environment` 指纹均匹配的两次运行才会合并。若某计划的 benchmark 或环境在运行中途发生变化，则只对最大的自洽子组做汇聚，并明确说明；`csbench query` 和 `csbench rollup` 暴露了指纹信息，使不可比（或仅在代码变更前提下可比）的情况一目了然。只有 `completed` / `unsupported` 的运行会贡献数值——`failed` 的运行会被计数，但没有可汇聚的结论。

Phase 1C **本身不**包含插件 API 版本范围、JSON Schema、合规套件或负载沙箱——这些属于 Phase 1D，现已在开放核心中发布（插件 API 版本范围 + 冲突检测、`RunSpec` / `ResultRecord` / manifest 的 JSON Schema、`csbench conformance`，以及负载沙箱第 1+2 层；详见 `ROADMAP.md` / `CHANGELOG.md`）。Phase 1C 仅对标量 `measurements` 做汇聚，不含时间序列。

## 分层结构

```
CLI (csbench)
  └─ Orchestrator (lifecycle state machine)
       ├─ Registry (entry-point domain discovery)
       ├─ BenchmarkSuite + Evaluator (+ Metric/Judge)  [基准契约,层①]
       │     └─ SuiteRunner (内部执行器:观测打包+纯函数评分)
       ├─ DomainPack → Adapter(s)                    [云生命周期,层②]
       │     └─ Adapter (setup/submit/teardown)      [per (domain, cloud)]
       │           └─ WorkloadEngine (JSONL, any language)  [per load generator]
       ├─ Schema (RunSpec / ResultRecord 0.4 / fingerprints / provenance)
       └─ Viewer (csbench serve: 记录/来源/轨迹)
```

## 插件契约

| 契约 | 职责 | 加载方式 |
| - | - | - |
| `DomainPack` | 为某类产品声明任务 + 适配器 | `clousight_bench.domains` 入口点 |
| `ProviderAdapter` | 制备 / 对接 / 拆除被测系统 | 由 DomainPack 引用 |
| `WorkloadEngine` | 将 manifest 描述的负载生成器作为子进程运行 | `src/clousight_bench/resources/workloads/<name>/manifest.yaml`，通过 `core/resources.py::reference_workload_path()` 解析 |
| `RuntimeProvider` | 为 `skeleton` 云适配器在真实模式下提供已接线、SDK 支持的传输层 | `clousight_bench.runtime_providers` 入口点，通过 `registry.get_runtime_provider(provider)` 解析 |
| `CampaignProbeHook` | 为每个 campaign 制备 / 同步 / 回收区域内负载探针（见下文"控制平面与数据平面"） | 通过注册 runtime provider 的 `campaign_probe_hook(provider)` 查找 |
| `ResourceReaper` | 为 `csbench sweep` 查找并删除带有 harness 标签的孤儿云资源 | `clousight_bench.resource_reapers` 入口点，通过 `registry.load_resource_reapers()` 解析 |

内置包与第三方包（包括闭源商业包）的加载方式完全相同——安装一个 Python 包或放入一个负载目录即可。对接真实云的领域包需要注册一个 `RuntimeProvider`（以及可选的 `CampaignProbeHook` 和 `ResourceReaper`）。开放核心定义这些抽象**并现已附带参考实现**——Aliyun AgentRun 和 AWS AgentCore 的 `RuntimeProvider` 及 `ResourceReaper` 均在源码树内注册（`pyproject.toml`），因此仅凭开放核心即可复现真实云路径。该接缝对第三方 / 商业 provider 以相同方式注册保持开放。

## 控制平面与数据平面

`execute`（接触云端）/ `score`（纯函数）的拆分同时也是控制平面 / 数据平面的拆分。`AgentRuntimeAdapter` 额外暴露一个接缝：`run_data_plane_probe(name, params) -> ObservationBundle`（`domains/agent_runtime/adapters/base.py`）——延迟类"数据平面"任务的 `execute()` 简化为 `return adapter.run_data_plane_probe("<probe>", {...})`，由此由*适配器*而非任务决定测量在何处运行。基础适配器将请求分发到进程内 packer 注册表（`domains/agent_runtime/dataplane_dispatch.py`），这正是 `local-sim` 和所有尚未接线的云所使用的路径。已接线的云可以改写此接缝，将整个测量发送到**目标区域内**运行的负载探针，使运营商的网络和系统代理不影响测量数值，同时返回相同的 `ObservationBundle` 供评分。`score()` 在两种路径下均无需改动。`run-plan` campaign 的探针生命周期由可选的 `CampaignProbeHook` 驱动（在任务循环的 `try/finally` 中启动、同步并回收；默认 `--probe local` 不触碰此路径）。开放核心定义该接缝和 hook 契约，**并在源码树内附带 Aliyun ECI 探针 carrier 及其云传输层的参考实现**；其他 carrier（其他云、第三方或商业）以相同方式注册。

## 证据层

`A` 文档 · `B` 环境观测 · `C` 受控测量 · `D` 市场宣传。
报告永远不将多个维度合并为单一分数。

## Suite / Evaluator 契约层

除领域-任务流水线外，Clousight Bench 还为**外部定义的基准套件**（如 SWE-bench）提供第二套契约：

* **`BenchmarkSuite`** 原封不动地驱动上游 harness，并返回不透明的 `RawArtifacts`（一个目录 + 命名文件的 manifest）。核心将 artifacts 视为黑盒；只有配对的 evaluator 才理解其内容。
* **`Evaluator`** 以纯函数方式（无云、无凭证）读取 `RawArtifacts`，返回 `dict[str, Measurement]`，其中每个 key 均以 `"<suite_id>.<metric_name>"` 命名空间（官方 evaluator）或 `"<evaluator_id>.<metric_name>"` 命名空间（自定义 evaluator）。
* **命名空间 / official 不变量**：`conformance.check_evaluator(evaluator, suite_id, measurements)` 验证官方 evaluator 仅在套件命名空间下发出 `official=True` 的测量值，自定义 evaluator 仅在自身 id 下发出 `official=False` 的测量值，且自定义 evaluator 不得抢注已注册套件的命名空间。返回 `list[CheckResult]`；CLI 侧可用 `csbench conformance --suite <suite_id>` 对套件的 mock 工件跑全部已注册 evaluator，也可与 `run_conformance` 组合用于 CI 门控。
* **`SuiteRunner` + `Provenance`**（内部执行器,不是插件契约）：将溯源元数据（suite\_id、suite\_version、evaluator\_id、unmodified 标志）线程化传递到每个 `TaskResult`，并折叠进基准指纹，确保来自不同套件版本或 evaluator 的记录永远不会混入同一分析池。
* **SWE-bench 试点**（切片 1）：`SweBenchSuite`（注册入口点 `swe-bench`）+ `OfficialSweEvaluator`（发出 `swe-bench.resolved` 和 `swe-bench.cost_per_resolved`）+ 内置 fixtures（支持离线 / CI 使用）。真实 Docker 支持的 `run()` 路径需要 `[swebench]` 可选包，并由 `@pytest.mark.slow` 门控。
* **SWE-bench Lite**（变体）：300 实例的精选子集，以 `SweBenchLiteSuite`（入口点 `swe-bench-lite`）+ `OfficialSweLiteEvaluator`（发出 `swe-bench-lite.resolved` / `swe-bench-lite.cost_per_resolved`）提供。它是旗舰的**薄子类**——harness、报告结构、agent-runtime 接线完全相同,仅数据集固定版本（`princeton-nlp/SWE-bench_Lite`）、内置真实 fixtures 与命名空间不同。这正是未来变体（Multimodal）复用的参数化接缝:变体只需覆盖 `suite_id` / `suite_version` / `fixtures_dir` / `dataset_name` 类属性,其余全部继承。
* **SWE-bench Multimodal**（变体）：来自真实 JavaScript / 前端可视化仓库的图像增强任务,以 `SweBenchMultimodalSuite`（入口点 `swe-bench-multimodal`）+ `OfficialSweMultimodalEvaluator`（发出 `swe-bench-multimodal.resolved` / `.cost_per_resolved`）提供。与 Lite 相同的薄子类接缝,外加第 7 个 fixture 字段 `image_assets`（图像 URL），由 `_load_instance` 透传给 agent。它**刻意绑定 `dev` split**:510 实例的 `test` 榜单 split 的 gold patch 是隐藏的（经托管 `sb-cli` 服务评分),而 102 实例的 `dev` split 带真实 gold patch、可本地 Docker 运行——与 Verified/Lite 一致。接入托管 test-split 路径是已记录的未来（受门控）增项。

## 当前领域

* `agent-runtime` — 会话、工具调用、故障恢复、可观测性、成本、隔离。所有任务均在 `local-sim` 上运行；完整任务列表和适配器状态**由下方注册表生成**（从不手动维护——见"文档维护"）。其覆盖范围包括制备、运行时行为、工具注册、可观测性、成本和租户隔离。适配器采用 `managed`/`transport`/`mode` 拆分，同一套代码既能驱动本地模拟运行时，也能在安装 runtime-provider 插件后驱动真实云；延迟类任务通过 `run_data_plane_probe` 接缝路由（见"控制平面与数据平面"）；能力探针触发 `CapabilityNotSupported` → 记录为一条发现，不会崩溃。

### 任务与适配器清单

### Benchmarks — 10 suites

| Benchmark | Version |
| - | - |
| `suite:gsm8k` | openai-gsm8k/sample-v1 |
| `suite:human-eval` | openai/openai\_humaneval\@7dce6050a7d6d172f3cc5c32aa97f52fa1a2e544 |
| `suite:mmlu` | cais-mmlu/sample-v1 |
| `suite:swe-bench` | princeton-nlp/SWE-bench\_Verified\@c104f840cc67f8b6eec6f759ebc8b2693d585d4a |
| `suite:swe-bench-lite` | princeton-nlp/SWE-bench\_Lite\@6ec7bb89b9342f664a54a6e0a6ea6501d3437cc2 |
| `suite:swe-bench-multimodal` | princeton-nlp/SWE-bench\_Multimodal\@aa2db68940196b6b59ae3f577faa0c25157bdd50 |
| `suite:tpc-c` | benchbase-2023 |
| `suite:tpc-ds` | duckdb-1.5.4/tpcds/sf1-ref-v1 |
| `suite:tpc-h` | duckdb-1.5.4/tpch/sf1-ref-v1 |
| `suite:ycsb` | ycsb-0.17.0 |

### `agent-runtime`

**Adapters:** `aliyun-agentrun` experimental · `aws-agentcore` skeleton · `huawei-agentarts` skeleton · `local-sim` reference · `volcengine-agentkit` skeleton

### `data-warehouse`

**Adapters:** `duckdb-local` reference

### `key-value`

**Adapters:** `ycsb-endpoint` experimental · `ycsb-local` reference

### `llm`

**Adapters:** `llm-endpoint` experimental · `llm-mock` reference

### `transactional-db`

**Adapters:** `benchbase-local` reference · `jdbc-endpoint` experimental

## 开发者预览就绪状态

* `reference` 和 `wired` 适配器可以执行。
* `experimental` 适配器可在预览注意事项下执行。
* `skeleton` 适配器可被发现，但在 preflight 之前会被拒绝。
* 五个领域各自带有一个可运行的参考适配器：`local-sim`、`duckdb-local`、`ycsb-local`、`benchbase-local` 和 `llm-mock`。Aliyun AgentRun（及 AWS AgentCore）的 `RuntimeProvider` 已在源码树内注册。`aliyun-agentrun` 适配器为 `experimental`：在 `mode: mock` 下可端到端运行，其 live 路径已完成一次真实云 campaign（`cn-hangzhou`，2026-08-15——25 个 `completed` + 2 个诚实的 `unsupported`）。该适配器尚未晋升为 `wired`（该状态保留给经过稳定、多次验证的 live 路径）；AWS AgentCore 及其他云仍为 `skeleton`。
* 内置负载位于 `clousight_bench.resources.workloads`，通过 `core.resources.reference_workload_path()` 解析，因此 wheel 安装和可编辑安装使用相同的文件。

Phase 1B 发布了 ResultRecord schema `0.4`（见上文"结果契约"）。插件 API 现为 `3.0`（OTel 原生追踪的破坏性变更;注册表拒绝声明 2.x 范围的插件）。

本仓库为公开仓库，采用 Apache-2.0 许可；包含完整的开放核心，**包括可复现机制**——Aliyun AgentRun `RuntimeProvider`、ECI 探针 carrier、`ResourceReaper`、Terraform 以及种子 `pricing` enricher。护城河在于*数据与服务*，而非私藏代码。独立的私有仓库 `clousight-bench-pro` 仅包含：

* `cb-pricing` — 更完整 / 更新鲜的私有价格数据源，由开放 `pricing` enricher 通过 `CLOUSIGHT_PRICING_DATA` 环境变量消费（非入口点）；
* `cb-dataservice` — 令牌门控的私有 / 留出数据集，通过 `clousight_bench.asset_resolvers` 入口点注册为 `PrivateAssetResolver`，以及托管对象存储上传（占位符）；
* `cb-adapters-enterprise` — 空占位符，用于未来的托管 SaaS 和私有云 / 国产栈适配器。

开放核心不导入、不依赖、也不因缺少上述组件而降级。

## 凭证与入门引导（便捷层）

原则：**永远不要求用户为基准测试单独生成一套密钥**——复用云 provider 自身的默认凭证链。

* `core/credentials.py::resolve_credentials(target, platform)` 仅**探测**凭证来源，从不读取或存储密钥值。解析顺序：`auth_env`（逃生舱——显式命名的环境变量）→ `profile`（命名 CLI profile）→ 标准环境变量 → 凭证文件。返回 `CredentialResolution(ok, source, identity_hint, remediation)`；`identity_hint` 仅包含变量名或 profile 名，从不包含密钥材料。
* `ProviderAdapter.resolve_credentials()` 委托给上述解析器（跨领域；`provider` 从 `target.provider` 或平台名前缀推断，例如 `aliyun-agentrun` → `aliyun`）。真实适配器运行时仍将凭证解析交由官方 SDK 的默认链处理。
* 每个 provider 的凭证链注册于 `PROVIDER_CREDENTIALS`（aws / aliyun / huawei / volcengine：标准环境变量、profile 环境变量、凭证文件、SDK 模块、文档链接）。
* CLI：`csbench init <provider>` 生成私有 `*.local.yaml` + `.env.example` 并写入 `.gitignore`（配置不含密钥）；`csbench doctor --config x` 执行逐步健康检查（provider → SDK 可导入？→ 凭证链可解析？→ `mock_base_url` 可达？localhost 立即失败），并为配置接入（config-connect）运行追加目标连通性探测：endpoint 的 TCP 可达性、Redis RESP `PING`，以及 `java -version` 门槛（BenchBase 需要 Java ≥ 17、YCSB ≥ 11）——全部带 SSRF 守护；mock 目标跳过这些探测。每一步都提供可操作的修复提示。

**PREFLIGHT 阶段**：上述检查不仅是独立命令——它们也是**一个生命周期阶段**。`core/preflight.py` 提供 `Check` / `PreflightReport` 和可复用的检查函数（`credential_check` / `sdk_check` / `mock_reachable_check`）；`doctor` 和编排器**共享相同的函数**（单一事实来源）。

* `ProviderAdapter.preflight() -> PreflightReport`：默认验证凭证 + provider SDK。可跨领域使用。
* `AgentRuntimeAdapter.preflight(task)`：在 `super()` 基础上，为**真实云平台**（非空 provider）追加 `mock_base_url` 可达性检查 + `check_permissions(task)`。local-sim（空 provider，自托管 mock）跳过两者，始终通过。
* 编排器在 RESOLVE 之后、SETUP 之前运行 `adapter.preflight(task)`；任何 CRITICAL 失败触发**提前退出**（不制备）：记录会得到一条 `StageError(stage="PREFLIGHT", code="preflight_failed")`，外加一条 `Finding(code="core.preflight_failed", severity="critical")`——其 `details.checks` 列出每一行检查——且记录的 `status` 为 `invalid`。`--skip-preflight` 禁用此门控。

### 最小权限：（基准测试 × 云）矩阵

给定基准测试所需的最低权限因云 provider 而异，因此权限验证**按任务映射**，职责分属两侧：

* **基准声明 WHAT**：suite 的 `required_permissions: tuple[str, ...]` 列出**抽象能力令牌**（与云无关）。令牌在 `domains/agent_runtime/permissions.py` 中定义：`session:create` / `session:state` / `tool:invoke` / `tool:register` / `trace:read` / `trace:export`。
* **适配器说明 HOW + 验证**：每个真实云适配器定义 `PERMISSION_MAP`（令牌 → 该云具体的最低 IAM/RAM 操作）。`required_actions(task)` 将任务的令牌解析为云特定的操作集；`_probe_permissions(actions)` 执行实际的授权检查（默认实现返回 `None` = 未验证 → 发出 WARNING，列出本次基准测试在该云上所需的最低操作；真实适配器覆盖此方法，调用 STS GetCallerIdentity / RAM policy 模拟 / dry-run，返回 `(ok, missing)`——任何缺失操作均为 CRITICAL 阻断）。

给定运行的最低权限等于适配器对任务令牌的映射。**新增一个基准测试维度**只需在任务上声明令牌；**新增一个云**只需在适配器上提供 `PERMISSION_MAP`——两侧完全解耦。`csbench doctor --domain <d> --platform <p> --task <t>` 可直接打印任意（基准测试 × 云）组合的最低操作列表。

## 数据集分发（三级，单一解析器）

基准测试不只是代码——它还包含**资产**（数据集、语料库、留出判分键）。方法论公开，数据分级，判分键私有。所有资产均由单一解析器提供；开放/私有边界纯粹取决于配置：

| 级别 | `source` | 位置 | 机制 |
| - | - | - | - |
| **内置开源** | `bundled` | `clousight-bench`（相对于负载/任务目录） | 体积小、许可证清晰，随仓库一起分发；存在 `sha256` 时进行验证 |
| **公开外部** | `remote` | 不内嵌；manifest 声明 `uri + sha256 + license` | 按需下载 → 哈希验证 → 缓存至 `~/.cache/clousight-bench/assets`；`license` 为必填（可审计） |
| **私有商业** | `private` | `clousight-bench-pro` / 数据服务 | 由注册在 `clousight_bench.asset_resolvers` 入口点的解析器处理（令牌认证下载）；若未安装解析器，则抛出 `NeedLicense`（清晰的用户提示，不崩溃） |

* **manifest 中的 `assets:` 段**（负载 / 任务）：
  ```yaml theme={null}
  assets:
    - name: tpcds-sf1
      source: remote            # bundled | remote | private
      uri: https://.../tpcds_sf1.tar.zst
      sha256: "…"
      license: TPC-EULA
      version: "1"
  ```
* **`core/assets.py`**：`AssetSpec` + `resolve_asset(spec, base_dir, cache_dir, private_resolver)`；`WorkloadEngine.resolve_assets()` 在运行前解析所有资产，并通过 `params["assets"]` 将 `{name: local_path}` 暴露给负载。
* **可复现且不泄露密钥**：`describe()` 将每个资产的 `identity()`（`name@version + sha256`）折叠进 `config_hash`——**只存储指纹，永远不存储内容**；判分键内容不进入记录。
* **判分键抗污染**：任务的方法论（如何评分）可以开源，而其留出键值声明为 `source: private`——"如何评分"公开，"答案键"私有，天然防止基准测试泄露。
* **扩展点**：私有解析器实现 `PrivateAssetResolver`（`name` + `resolve(spec, cache_dir)`）并通过 `clousight_bench.asset_resolvers` 注册；开放核心不提供实现。`clousight-bench-pro` 的 `cb-dataservice` 组件提供 `DataServiceAssetResolver`（`CLOUSIGHT_BENCH_TOKEN` 认证 + sha256 验证）。

## 数据契约与扩展点

`ResultRecord` 携带三个相互独立、互不覆盖的通道；消费者按需读取：

* `measurements`（标量分数）——报告层用于汇聚和比较的数值（不存在顶层 `metrics` 字段）。
* `series`（时间序列，`{name: [[t, value], ...]}`）——高频采样，如每秒延迟或 GPU 利用率。
* `artifacts`（`{"kind", "path"/"uri", "media", "sha256"}` 指针）——大文件，如 OTel 追踪和日志包；`record.json` 中只存储指纹，不存储内容。

负载协议（WorkloadEngine stdout JSONL）在现有 `metric`/`log`/`result` 事件基础上新增两种事件类型，相互不干扰：

```jsonl theme={null}
{"type": "sample", "series": "latency_ms", "t": 1.0, "value": 87.2}
{"type": "artifact", "kind": "otel_trace", "path": "trace.json", "media": "application/json"}
{"type": "result", "ok": true}
```

`sample` 事件按序列名累积进 `WorkloadResult.series`；`artifact` 事件的 `sha256` 由引擎从 `workload_dir/path` 的文件内容计算——负载本身无需哈希任何内容。

**持久化**：`record.json` 保持其历史路径 `results/<domain>/<platform>/<task_id>-<run_id>.json` 不变（`core/store.py::ResultStore` 负责此职责；`orchestrator._persist` 委托给它，签名稳定）。当可选的 `[store]` 扩展（`duckdb` + `pyarrow`）已安装且记录含有 `series` 数据时，序列会被外部化为运行目录下的 `series.parquet` 宽表，`record.json` 中的 `series` 字段被改写为指针 `{"$parquet": "<relative_path>"}`。若未安装该扩展，序列将内嵌于 `record.json`（小数据量下无损）。

Parquet 宽表列（`cb-dataservice` 和 SaaS 网页前端消费的稳定握手）：

```
run_id | domain | task_id | platform | benchmark_fingerprint | series | t | value | unit
```

`unit` 列约定：`ResultStore` 写入 Parquet 时，从 `record.measurements[<name>].unit` 读取每个序列 `<name>` 的单位字符串——即与序列同名的测量值（例如 `latency_ms` 序列的单位取自 `latency_ms` 测量值）；当不存在同名测量值时，该列为空字符串 `""`。这是**隐式约定，不强制执行**——省略该字段仍可持久化行；`unit` 列仅为空。

`ResultStore.query_series(sql=None, glob="**/series.parquet")` 通过 DuckDB 直接对 `results_dir` 下所有 Parquet 文件运行 SQL；`glob` 路径作为绑定参数传给 `read_parquet(?)`（无字符串拼接）；当 `[store]` 扩展缺失时抛出 `ImportError`。

**扩展点**：`clousight_bench.enrichers` 入口点组托管 `ResultEnricher` 子类——`name: str` + `enrich(self, record: ResultRecord) -> ResultRecord`。`orchestrator.execute(spec, results_dir=None, enrich=True, preflight=True, publisher=None, …)`（`publisher=` 关键字参数为 PUBLISH 阶段注入可选的 `ResultPublisher`）在记录构建完成后、`_persist` 之前依次调用它们，顺序由 `registry.load_enrichers()`（按 `name` 排序以确保确定性执行顺序）返回的列表决定。核心不提供任何 enricher 实现（如成本估算）——商业插件通过入口点注入；`csbench run --no-enrich` 跳过整个链路。

**插件兼容性契约**：`clousight_bench.PLUGIN_API_VERSION = "3.1"`（SemVer）。只有当 schema 字段、入口点组名称或 `ResultEnricher`/`ResultStore` 签名发生不兼容变更时，MAJOR 才会递增。**包版本与插件 API 版本是两个独立维度**：`clousight_bench.__version__`（当前为 `0.7.0`）随每次发布递增；`PLUGIN_API_VERSION` 仅在插件面向接口有破坏性变更时移动——两者不同步演进。Phase 1A 商业插件的 `pyproject.toml` 针对当前包版本固定为 `clousight-bench>=0.7,<0.8`（早期设计文档中出现的 `>=1.0,<2.0` 形式是针对 `PLUGIN_API_VERSION` 大版本的目标状态草稿，在 0.2 开发者预览发布时未更新；此处已更正）。

**Pro 的三个扩展边界**（由 `clousight-bench-pro` 通过入口点注入；开放核心不提供实现）：

| 接口 | 职责 | Phase 1A 状态 |
| - | - | - |
| `ResultEnricher`（`core/plugin.py`） | 向已生成的 `ResultRecord` 追加派生指标（如成本估算） | 已实现：`clousight_bench.enrichers` 入口点，由 `registry.load_enrichers()` 加载 |
| `PrivateAssetResolver`（`core/plugin.py`） | 解析私有/许可资产（数据集、留出判分键） | 已实现：`clousight_bench.asset_resolvers` 入口点；`cb-dataservice` 的 `DataServiceAssetResolver` 已注册 |
| `ResultPublisher`（`core/publish.py`） | 可选的结果发布、签名和团队报告 | 已实现：`core/publish.py` 中的 `class ResultPublisher(ABC)`；PUBLISH 是一个生命周期阶段，在 PERSIST 之后运行并写入仅追加的回执。通过 `orchestrator.execute(publisher=...)` 注入——publisher 没有入口点组 |

## 文档维护

本文档的一部分由脚本生成，其余部分需要你手动与行为变更同步——在*同一次变更*中更新。

* **生成块**位于 `{/* BEGIN generated:… */}` / `{/* END generated:… */}` 标记之间（目前为基准套件与适配器清单）。它们由领域注册表渲染——与 `csbench list --json` 的数据来源相同——通过 `scripts/gen_docs.py` 生成。永远不要手动编辑标记内的内容。新增/重命名基准套件或更改适配器状态后，运行 `python scripts/gen_docs.py` 并提交结果。CI 会运行 `python scripts/gen_docs.py --check` 和 `tests/test_docs_inventory.py`，因此过时的块（手写的"28 tasks"与注册表不符）会导致构建失败，而不会被发布。
* **散文段落**（标记之外的所有内容）是行为变更完成定义的一部分：当某个任务的测量形状、适配器契约或生命周期规则发生变化时，在同一 PR 中更新描述它的段落。比文档落后一个版本更糟糕的是没有文档——审阅者会停止信任它。每次编辑应尽量小而聚焦；生成块吸收机械性事实，散文只需承载意图。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.