diff --git a/.github/workflows/core-candidate.yml b/.github/workflows/core-candidate.yml index 9f56ba18..f437c5d9 100644 --- a/.github/workflows/core-candidate.yml +++ b/.github/workflows/core-candidate.yml @@ -56,6 +56,8 @@ jobs: python-version: '3.12' - name: Install fixed semantic references run: python -m pip install --require-hashes -r experiments/differential/requirements.txt + - name: Install fixed CPU observer + run: python -m pip install -r benchmarks/requirements.txt - name: Select fixed Rust toolchain run: rustup default 1.92.0 - name: Build locked candidate diff --git a/.github/workflows/r54-cost.yml b/.github/workflows/r54-cost.yml new file mode 100644 index 00000000..e84887ef --- /dev/null +++ b/.github/workflows/r54-cost.yml @@ -0,0 +1,55 @@ +name: Frozen R54 hosted cost batches + +on: + push: + branches: ['perf/grapheme-hosted-r54'] + paths: + - '.github/workflows/r54-cost.yml' + - 'experiments/measure/*.py' + - 'experiments/candidates/alacritty/Cargo.toml' + - 'experiments/candidates/alacritty/src/measure.rs' + +permissions: + contents: read + +concurrency: + group: r54-cost-${{ github.ref }} + cancel-in-progress: false + +jobs: + fixed-cost: + # A code/evidence correction must not silently launch replacement samples. + if: github.event.created == true + runs-on: windows-2025 + timeout-minutes: 30 + env: + PYTHONUTF8: '1' + CARGO_HOME: ${{ github.workspace }}/local/r54-cargo-home + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: '3.12' + - name: Install fixed host observer + run: python -m pip install -r benchmarks/requirements.txt + - name: Select fixed compiler + run: rustup default 1.92.0 + - name: Verify runner and publication contracts before measurement + run: python -m unittest experiments.measure.test_r54_contract.CostPlanTests experiments.measure.test_r54_batch experiments.measure.test_r54_hosted -v + - name: Build audited pair and execute the two frozen batches + id: measure + run: python -m experiments.measure.r54_hosted --out local/r54-raw + - name: Prepare the reviewed synthetic artifact whitelist + if: always() && steps.measure.outcome != 'skipped' + run: python -m experiments.measure.r54_publish --raw local/r54-raw --out local/r54-public + - name: Preserve complete or failed synthetic evidence + if: always() && steps.measure.outcome != 'skipped' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: r54-cost-${{ github.run_id }}-${{ github.run_attempt }} + retention-days: 30 + if-no-files-found: error + path: local/r54-public/ diff --git a/.github/workflows/r54-cpu.yml b/.github/workflows/r54-cpu.yml new file mode 100644 index 00000000..1b398c4b --- /dev/null +++ b/.github/workflows/r54-cpu.yml @@ -0,0 +1,50 @@ +name: Bounded R54 hosted CPU diagnosis + +on: + push: + branches: ['perf/grapheme-cpu-hosted-r54', 'perf/grapheme-cpu-hosted-r54-setup2', 'perf/grapheme-cpu-hosted-r54-entry3'] + +permissions: + contents: read + +concurrency: + group: r54-cpu-${{ github.ref }} + cancel-in-progress: false + +jobs: + cpu-boundary: + # Evidence or exporter fixes must not silently create replacement observations. + if: github.event.created == true + runs-on: windows-2025 + timeout-minutes: 30 + env: + PYTHONUTF8: '1' + CARGO_HOME: ${{ github.workspace }}/local/r54-cargo-home + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: '3.12' + - name: Install fixed CPU observer + run: python -m pip install -r benchmarks/requirements.txt + - name: Select fixed compiler + run: rustup default 1.92.0 + - name: Verify build, accounting and publication boundaries + run: python -m unittest experiments.measure.test_r54_contract.CostPlanTests experiments.measure.test_r54_batch experiments.measure.test_r54_hosted experiments.measure.test_r54_cpu_diagnostic experiments.measure.test_r54_cpu_hosted -v + - name: Build audited pair and execute one six-observation diagnosis + id: diagnose + run: python -m experiments.measure.r54_cpu_hosted --out local/r54-cpu-raw + - name: Prepare the synthetic artifact whitelist + if: always() && steps.diagnose.outcome != 'skipped' + run: python -m experiments.measure.r54_publish --experiment cpu-boundary --raw local/r54-cpu-raw --out local/r54-cpu-public + - name: Preserve complete or failed synthetic evidence + if: always() && steps.diagnose.outcome != 'skipped' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: r54-cpu-${{ github.run_id }}-${{ github.run_attempt }} + retention-days: 30 + if-no-files-found: error + path: local/r54-cpu-public/ diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7f0a5c..ac539c1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,7 +351,7 @@ add_test(NAME edit_csi_core COMMAND edit_csi_test) endif() termforge_add_python_test(core -m unittest experiments.core.test_core experiments.core.test_unicode -v) termforge_add_python_test(recovery -m unittest experiments.core.test_recovery -v) - termforge_add_python_test(measure -m unittest experiments.measure.test_measure experiments.measure.test_r54_contract.CostPlanTests -v) + termforge_add_python_test(measure -m unittest experiments.measure.test_measure experiments.measure.test_r54_contract.CostPlanTests experiments.measure.test_r54_batch experiments.measure.test_r54_hosted experiments.measure.test_r54_cpu_diagnostic experiments.measure.test_r54_cpu_hosted -v) termforge_add_python_test(binary_paths -m unittest experiments.test_build_paths -v) termforge_add_python_test(conpty_margin_contract -m unittest experiments.conpty.test_margin_contract -v) termforge_add_python_test(benchmark_tools -m unittest discover -s benchmarks/tests -v) diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 41563c45..1cce159f 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -83,6 +83,10 @@ R1 固定总出口的源码为 `ce9b24d`,早期 `2dc8d6d` / `9b16acc` 仍分 | 持续输出中断的原失败记录 | [#36](https://github.com/JavaZeroo/termforge/issues/36)、[ConPTY 输入机制研究](research/conpty-ctrl-c-input.md)、[原 R1 历史证据](../experiments/shell/results/2026-10-23-r1-foundations/validation.json) | 原 Windows 11 回归中的 normal/resize 超时与 ETW/插桩观察;调试扰动及管道读完成缺口保留。不是当前全套结果,后续未复现不能抹去此失败。 | | controller B 正常结束后 cmd 退出未验收(#44) | [原失败脱敏记录](../experiments/shell/results/2026-10-26-controller-exit/validation.json)、[新退出诊断及有界重放](../experiments/shell/results/2026-10-26-controller-exit-observation/validation.json)、[关闭合同](https://github.com/JavaZeroo/termforge/issues/44) | 原 child 收完 32B、退出 0/join/Job0;cmd 只留下 close 后 0xC000013A,close 前未知。新观察分列输入、wait、pre-close 与清理;多轮未复现不构成历史修复,继续阻塞 #38。 | | 原 10 秒纯夹具超时的阶段保留(#40 未关闭) | [限定合同](research/inventory-timeout-checkpoints.md)、[正常与受控超时对照](../research/ime/results/2026-10-26-inventory-timeout-checkpoints/validation.json) | 保持原 10 秒期限;超时后保留有界阶段、原异常和回收。受控挂起对照已通过,但历史 CI 的阶段/根因仍未知,#40 不因此关闭。 | +| 固定字素成本的批次协议与执行 | [R54 v1 合同和构建/环境记录](../experiments/measure/R54-COST.md)、[执行器](../experiments/measure/r54_batch.py)、[合成进程回归](../experiments/measure/test_r54_batch.py)、[实际预检及限定验证](../experiments/measure/results/2026-10-27-r54-runner/validation.json) | 固定两个种子、各 112 个位置,字节原样保留、首错停止、完整批次后逐组配对统计。共享工作站十区间活动预检未通过,未启动成本样本;测量相关 25 项测试通过。5% 是环境诊断护栏,并非产品性能容差;实际批次、扰动归因及用户预算确认由 #54 接续。 | +| 首次 hosted 字素成本批次 | [来源、停止位置与归档](../experiments/measure/results/2026-10-27-r54-hosted/README.md)、[机器可读记录](../experiments/measure/results/2026-10-27-r54-hosted/validation.json) | 两份固定构建与4次sanity通过,空闲预检通过;第24位置的未归属CPU活动估计5.410%触发原5%护栏,保留23个有效记录和1个失败记录,第二批未启动。全部24次输出正确,但批次不完整,不生成配对成本摘要;临时VM逐进程归因、完整受控批次和预算仍未完成。 | +| CPU计量边界诊断(#54) | [固定六位置及归档](../experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md)、[独立复核](../experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py) | 同一共享Windows宿主、原已审计构建的六次输出和来源核验通过;分别保留完整子进程、内层区间、runner和全机计数。区间外CPU为0.078125–0.109375s,仍有明显未归属活动,不算空闲成本批次,不反推旧hosted原因或修改原5%护栏。 | +| 新hosted VM的CPU分量(#54) | [预声明](../experiments/measure/R54-CPU-HOSTED.md)、[六位置/来源/完整归档与只读复核](../experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md) | 新构建和六个输出均通过;完整扣除自身/runner后余额仍为3.333%–5.263%,不归因旧事件、不作成本验收。准备失败与未创建作业的入口分别保留,未重跑或补样。 | | Node 纯夹具阶段与失败判定(#144 未关闭) | [工具合同](research/node-contract-deadline.md)、[固定三 case 与身份](../research/ui/results/2026-10-29-node-contract-deadline/README.md) | 固定02aac源码的一次正式批次:正常passed0/23cp、受控挂起timed_out99/6cp、受控异常failed1/11cp,均根回收/Job0;CLI0只表示预定义结果齐全。协议尾、版本漂移及离线出口白名单P2的纯反例分列;出口普通/-O各15项纯测试,原件和旧公开字节保留。旧CI根因/整程硬界仍null,不接原CI执行入口,新增CTest仅注册纯数据回归,不关闭#144/#38。 | | 纯 AST 夹具的生命周期记录 | [#40](https://github.com/JavaZeroo/termforge/issues/40)、[计时、进度与回收验证](../research/ime/results/2026-10-26-inventory-lifecycle/validation.json)、[专用 CI 40253727745](https://github.com/JavaZeroo/termforge/actions/runs/40253727745) | 旧 10 秒 AST 夹具及专用 CI 记录启动、AST 和 Job0;原超时根因未确定。诊断增强见 #40 阶段保留,不由一次 CI 成功结项。 | | 原生 margin 逐字段合同与修复候选(#56 仍开放) | [合同、源码与路径](research/conpty-native-margin-path.md)、[独立 oracle](../experiments/conpty/margin_contract.py)、[固定运行](../experiments/conpty/results/2026-10-26-native-margin-path/validation.json) | 四阶段文字/格属性/光标独立判据;系统与官方原生 final 仍红,官方80列两次原5秒生命周期失败保留。共享单 margin 源码支持小 MIT fork 候选;patch 仅可应用,未编译、未运行、不计修复。 | @@ -170,4 +174,4 @@ python -m unittest experiments.shell.test_workflow_sessions -v | 证据 | 来源/复现入口 | 可以支持的判断 | |---|---|---| | 固定功能和发行目标 | [固定 README](https://github.com/kingToolbox/WindTerm/blob/a8336c1d1e3dd1a981500dae8ef9eb42066de95f/README.md)、[2.7.0 release](https://github.com/kingToolbox/WindTerm/releases/tag/2.7.0)、[平台范围台账](../research/windterm-2.7.0-platform-scope.md)、[发行包校验流程](../benchmarks/tools/WINDTERM-LAB.md) | 官方功能声明与包身份;#23 已用固定源码、release API 和资产元数据固定四类发行平台/架构及声明下限,保留安装说明差异和未知系统。声明仍需运行时验收,不跟随未来 master 自动扩大范围 | -| 已公开/未公开边界 | [源码与发行物调查](research/windterm-audit.md) | 固定公开源码主要是组件/修改,无法据此构建完整 WindTerm;静态依赖与符号不证明运行时实现或授权 | \ No newline at end of file +| 已公开/未公开边界 | [源码与发行物调查](research/windterm-audit.md) | 固定公开源码主要是组件/修改,无法据此构建完整 WindTerm;静态依赖与符号不证明运行时实现或授权 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index a703247e..023186e6 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -80,7 +80,7 @@ Qt 构建显式准备并部署[固定微软 CRT 14.51.36247.0](../third_party/ms | 自动正确性与构建 | **限定验收**:Windows shell/ConPTY/呈现及 Windows/Linux 核心 CI、差分/恢复/队列/生命周期场景 | 产品打包、干净机器使用、依赖升级、长期回归和更广平台矩阵;#40 的 CI 纯夹具超时、窄窗口选择 #73、四 shell 原事件 #80、hosted 准备 #81 和[复制夹具 OpenClipboard 5 #85](https://github.com/JavaZeroo/termforge/issues/85) 等独立失败仍未闭合;#72 保存判据与 #80 同步条件的限定修正不代替其他配置或未知根因验收 | R2 / R4 | | Windows 产品交付 | **实现中,未验收**:首个 [v0.1.0-alpha.1 预发布](releases/v0.1.0-alpha.1.md)提供 Windows x64、静态 MSVC 运行库、附资源许可和构建身份的免安装 ZIP;未签名,已验环境限 Windows 11 本机与 Server 2025 CI,布局仍写入 LOCALAPPDATA | 这是 R1 体验包,尚非 R2 日常开发版;干净机器与更广平台矩阵、签名/安装、配置升级及持续运行仍待完成。Windows 10 未验收,Server 2022 已知资源门槛未过 | R2 / R3 | | Linux/macOS 与其他架构 | **未实现**客户端;Linux 核心 CI 已有。[官方平台范围台账](../research/windterm-2.7.0-platform-scope.md)已固定 2.7.0 的 Windows x86/x64、macOS x64、Linux x64 资产及声明下限;这不是 TermForge 支持或实测结论 | 最终需真实 Linux/macOS 客户端;平台调查可提前进行,整合及完整验收排在 Windows 11 x64 完整版本之后。其他系统版本、最低版本和架构由用户后续明确;旧系统及 ARM 等保持调查记录,不自动承诺或排除,也不能由核心 CI 推断产品支持 | 持续调查;R5 全平台出口 | -| 吞吐、响应、内存与稳定性 | **实现中,未验收**:已有机制实验、连续 CPU 区间校准与合成回归;自主C++ [recovery双构建准备](../experiments/measure/results/2026-10-29-recovery-core-prepare/README.md)已核对包装有无、构建/对象身份及24短正确性样本,旧strict结果分列。候选字素改动的历史受限诊断中 project/interactive 吞吐中位数约下降 16%/9%,由 [#54](https://github.com/JavaZeroo/termforge/issues/54) 跟踪 | 共享宿主、历史allocator包装与缺少真实呈现/输入时延端点使旧诊断不构成正式预算;新双构建仅为准备,未形成正式成本样本。仍需同负载/同正确性、受控对照、观测开销和长期/多会话公平性;可靠原版基线后提出逐项预算及允许波动范围,**由用户明确确认**。与 WindTerm 的实际差距**待取证** | 当前优先;贯穿 R1–R5 | +| 吞吐、响应、内存与稳定性 | **实现中,未验收**:已有机制实验、连续 CPU 区间校准与合成回归;候选字素改动的受限诊断中 project/interactive 吞吐中位数约下降 16%/9%,由 [#54](https://github.com/JavaZeroo/termforge/issues/54) 跟踪。[冻结批次执行器](../experiments/measure/R54-COST.md#冻结批次执行器windows)核对构建来源、原始输出、停止规则与逐轮配对统计;[独立 hosted 构建与实测](../experiments/measure/results/2026-10-27-r54-hosted/README.md)已验证来源和失败保留,但首批因环境护栏停止,尚无完整成本批次;[共享宿主CPU诊断](../experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md)及[新hosted六位置](../experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md)分别记录完整子进程/执行器分量;后者仍有3.333%–5.263%未归属余额,不改原护栏或判成本通过 | 旧诊断的共享宿主、allocator 包装与缺少真实呈现/输入时延端点使其不构成正式预算;新的不完整批次也不作吞吐结论。仍需同负载/同正确性、无插桩对照、观测开销和长期/多会话公平性;可靠原版基线后提出逐项预算及允许波动范围,**由用户明确确认**。与 WindTerm 的实际差距**待取证** | 当前优先;贯穿 R1–R5 | | 大历史与动态内存压缩 | **未实现**产品能力;WindTerm 压缩属于官方性能声明,内部机制尚未证实 | 1/10/100 万行等明确规模下的内存/滚动/搜索/淘汰实验;根据瓶颈决定独立存储/压缩实现 | R4 | | 完整目标验收 | **待取证**:已有固定版本和局部观察,没有完整行为/性能对照矩阵 | 先在 Windows 11 x64 完整验收功能/行为/UI/性能及独立构建,再完成 Linux/macOS 全平台出口;重要差异和性能容差须有用户确认,Windows 通过不代表全平台完成;不依赖 WindTerm 二进制或专有资源 | R5 分平台出口 | diff --git a/experiments/measure/R54-COST.md b/experiments/measure/R54-COST.md index f4a16c0b..d9abf9f2 100644 --- a/experiments/measure/R54-COST.md +++ b/experiments/measure/R54-COST.md @@ -59,4 +59,39 @@ $env:TERMFORGE_R54_UNWRAPPED = '' python -m unittest experiments.measure.test_r54_contract.OptionalAllocatorBuildSmoke -v ``` -该批只交付准备合同与可完全移除包装的构建机制。两个实际 build manifest/EXE、真实空闲环境证据、冻结计划执行器和重复样本、成本归因、数值预算与优化验收仍由 #54 接续;生成计划或短 sanity 都不关闭该 issue。 +## 冻结批次执行器(Windows) + +[r54_batch.py](r54_batch.py) 执行原 v1 的完整种子顺序,没有重试、补样或缩短时长开关。它只读取已有构建;不会在计时阶段编译。`samples_complete` 只表示样本及顺序通过验证,外层 `complete_diagnostic` 还要求构建绑定与本节环境检查通过;二者均保留 `performance_gate=not_evaluated`,不等于性能验收。 + +执行前需要保留以下私有构建目录。其 v1 格式由 [r54_inputs.py](r54_inputs.py) 检查,不接受只填源码 SHA 和 EXE SHA 的简化声明: + +- `build-plan.json`:`termforge-r54-local-build-plan-v1`,包含原计划文件哈希、共同 harness 提交、编译器/工具版本、环境变量、完整构建命令、两份原提交/树及每个候选目录跟踪文件覆盖前后的 SHA。 +- `source-baseline/`、`source-candidate/`:固定 `.3/.4` checkout,只覆盖根 Cargo.toml 和 src/measure.rs;原锁文件和 vendor 不变。 +- `baseline-build.json`、`candidate-build.json`、各自 `*-build.stdout/stderr`、`*-harness.patch` 和 `*-identity.stdout/stderr`:实际构建、原始日志、覆盖 diff 与编译模式。 +- `build-audit.json`:原构建审计,包括 receipt 哈希、实际 linker 路径和 SHA、核心来源、版本与二进制身份。执行器重新核对当前源文件、全部日志和 EXE,再实际运行 `--identity`。这是一组可审计记录,不是二进制来源的密码学证明。 + +当前绑定明确支持已有 Rust 1.92.0 / x86_64-pc-windows-msvc / Release / locked/offline / no-default-features / jobs=1 / 独立 target 配置;其他构建需要另行形成记录和审查,不能静默混入。该准备格式及构建产物保持在 `local/`,不包含在公开 CI 的默认产物中。 + +环境声明 JSON 必须显式填写 `host_kind`(`shared_workstation`、`hosted_vm` 或 `dedicated_host`)、`known_concurrent_work` 字符串列表和 `no_known_concurrent_build_or_test=true`。声明不能取代实际观测。源工作树必须已提交且干净,输出目录必须不存在。 + +实际 Windows 采样依赖 [benchmarks/requirements.txt](../../benchmarks/requirements.txt) 固定的 psutil;纯统计和计划检查仅依赖 Python 标准库,不因导入环境统计函数而要求安装宿主采样依赖。 + +```powershell +python -m experiments.measure.r54_batch --plan local/r54-batch/plan-v1.json --build-bundle local/r54-batch --environment local/r54-environment.json --seed 20261026 --out local/r54-run-20261026 +# 第二批另行启动,使用原第二种子和新目录;第一批失败时先保留并调查原因。 +python -m experiments.measure.r54_batch --plan local/r54-batch/plan-v1.json --build-bundle local/r54-batch --environment local/r54-environment.json --seed 20261027 --out local/r54-run-20261027 +``` + +环境护栏 `termforge-r54-environment-guard-v1` 在任何正式成本样本之前冻结:一次观测十个连续约一秒区间,**每个**区间全机 busy CPU 必须不超过 5%;任一区间失败即保留完整预检,不启动成本负载,也不循环等到一次通过。该 5% 是保守的环境诊断筛选值,**不是用户批准的性能回归容差或 WindTerm 等价标准**。两批之间仍需重新观察;宿主背景工作发生变化是新环境证据,不能替换原失败。 + +Windows 1–64 逻辑 CPU 上使用 [GetSystemTimes](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getsystemtimes) 原始 user/kernel/idle 计数,busy 为 user+kernel−idle,保留原始计数;kernel 已含 idle,不重复累加。每个子进程前后继续记录全机计数,将全机 busy 减去**内层**测量 CPU 后的余额作为背景活动的保守上界估计(包含测量区间外的准备、oracle、序列化及 runner 工作)。余额为负或其占全机总 CPU 时间超过 5% 时,保留该样本并停止批次。不从主样本成本扣除该余额,不把它当成精确的进程归因。该护栏不能证明虚拟机物理宿主独占、频率稳定或完全无干扰。 + +原始目录保留 OS build/UBR、CPU、逻辑/物理核数、电源策略前后、进程 CPU 身份、并行工作声明、所有环境区间及执行器源码身份。进程记录不读取命令行或秘密环境变量,但仍属私有证据;公开前须脱敏审核。 + +每个计划位置先落盘命令和编号,再把 stdout/stderr **直接写入二进制文件**;超时、无效 UTF-8、坏 JSON、非零退出和 oracle 错误都保留原字节与 manifest。子进程超时按原 90 秒处理;监督对象是不会再派生子进程的测量 EXE,不据此声称 Windows Job 回收。中断保留当前位置;不存在续跑或覆盖旧目录的入口。每个有效样本重查 EXE 哈希,结束后再核对源/构建证据及电源策略。 + +全部 112 个位置完整有效后才生成统计;缺失、重复、乱序或失败不能得到成功摘要。摘要逐批、逐 route/chunk 列出七个配对 CPU ns/B 比值,以及各源 CPU/墙钟 ns/B 全部值、中位数、范围和 MAD。合成进程测试覆盖整个顺序、原始字节和停止规则,其虚构时长只验证统计与进程协议,绝不作为性能样本。 + +实际受控重复样本、allocator/projection 扰动归因、预算提案和优化验收仍由 #54 接续;构建、短 sanity、执行器测试或环境预检均不关闭该 issue。 + +独立 Windows 虚拟机上的来源构建、顺序执行、白名单产物和宿主限制见 [R54 hosted 流程](R54-HOSTED.md)。该入口复用本合同,不把CI可运行或环境预检通过解释为产品性能验收。 diff --git a/experiments/measure/R54-CPU-DIAGNOSTIC.md b/experiments/measure/R54-CPU-DIAGNOSTIC.md new file mode 100644 index 00000000..49f54d10 --- /dev/null +++ b/experiments/measure/R54-CPU-DIAGNOSTIC.md @@ -0,0 +1,37 @@ +# R54:CPU 计量边界的独立诊断 + +本项接续 [#54](https://github.com/JavaZeroo/termforge/issues/54) 的首次 hosted 停止事件:原批次第24个位置按 v1 口径得到 5.410% 未归属活动估计,超过原5%环境护栏。原记录没有整个子进程及执行器CPU,无法事后区分自身区间外工作和其他guest活动。该事件、失败状态、[v1合同](R54-COST.md)及两批各112位置均保持;此诊断不补样、不重试旧批次、不判性能通过。 + +## 预声明问题与执行范围 + +按以下预测分别记录,而不是先假定护栏误报: + +1. 若自身区间外工作有贡献,完整子进程CPU会高于其内部连续测量区间CPU;差值包括启动、准备、oracle、输出和退出,不能仅凭差值细分各阶段。 +2. 若执行器有贡献,其同一外部区间的进程CPU会大于零。 +3. 扣除前两项后的余额仍可能包含其他guest活动、系统归账粒度和非原子采样边界;余额保留正负,不称为精确后台进程CPU。物理宿主争用和频率仍未知。 + +固定一次六位置序列为 `.3, .4, .4, .3, .3, .4`,全部使用原 `interactive` 22,000B 语料、chunk=1、observe=0、内部CPU/墙钟至少2秒及原正确性校验。沿用已审计的共同harness与两份无allocator包装EXE,不重新构建。只比较计量范围,**不计算A/B成本比或吞吐变化,也不运行空闲合格筛选**。允许在如实声明的共享宿主研究这些计数;这不把共享宿主升级为成本验收环境。 + +每个进程90秒期限,超时终止直接子进程并最多等待5秒;退出未确认、计数缺失、输出错误或来源变化立即停止,不补跑。固定核心EXE不派生进程,本工具只支持这个直接子进程边界,不宣称通用进程树计量或回收。诊断状态 `accounting_complete` 只说明六次记录/正确性/来源完整,始终 `performance_gate=not_evaluated`。 + +## 计数与证据 + +`GetSystemTimes` 先取全机计数,再取runner的 `process_time`,然后启动并等待直接子进程;退出后通过保留的进程句柄读取 `GetProcessTimes`,再取runner和全机末计数。全机计数只支持1–64逻辑CPU。两个采样端点不是原子操作,读取进程计数本身也有成本;诊断不声称无扰动。 + +每个观察保留全机原始前后计数、runner前后CPU、子进程创建/退出FILETIME及user/kernel/总CPU、原始stdout/stderr、内层区间和以下余额: + +- `inner_only_balance = guest_busy - inner_cpu`:本次插桩区间的内层单项扣除,不冒充旧批次的原值。 +- `child_outside_inner = child_lifetime_cpu - inner_cpu`。 +- `unassigned_balance = guest_busy - child_lifetime_cpu - runner_cpu`:保留负值,不据此修正成本或设新护栏。 + +Windows进程对象在外部仍持有句柄时可于退出后查询,依据 [GetProcessTimes](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes) 和[进程终止语义](https://learn.microsoft.com/en-us/windows/win32/procthread/terminating-a-process)。诊断限定Windows CPython3.12,通过Popen持有的句柄避免退出后按PID查询;该私有接口依赖及句柄生命周期按[CPython3.12.10源码](https://github.com/python/cpython/blob/v3.12.10/Lib/subprocess.py)核对,不能静默扩展到其他runtime。 + +入口要求已提交且干净的工具源码、显式环境声明、原计划及完整构建包,运行前后复核全部来源、编译产物及实际`--identity`。输出目录必须不存在;过程、失败和中断记录保留。原始本地路径和PID留local,公开时单独审核白名单、路径脱敏和原字节流哈希,不递归上传工作目录。 + +```powershell +python -m experiments.measure.r54_cpu_diagnostic ` + --plan <原plan-v1.json> --build-bundle <原构建包> ` + --environment <含host_kind与known_concurrent_work的JSON> --out <全新目录> +``` + +本次结果只回答声明宿主上的计量问题,不能补造旧hosted事件的原因。是否改变后续环境测量方法须先依据诊断另定协议,不能倒改原v1或用新口径将旧失败改绿。完整成本批次、allocator/projection及组件归因、数值预算提案与用户确认仍由 #54 接续。 diff --git a/experiments/measure/R54-CPU-HOSTED.md b/experiments/measure/R54-CPU-HOSTED.md new file mode 100644 index 00000000..4a63896b --- /dev/null +++ b/experiments/measure/R54-CPU-HOSTED.md @@ -0,0 +1,29 @@ +# R54:独立 hosted CPU 分量诊断 + +本次延续[六位置CPU边界合同](R54-CPU-DIAGNOSTIC.md),回答在一个新GitHub-hosted Windows VM中,自身区间外CPU、runner CPU与全机余额分别多大。共享工作站的[一次实际诊断](results/2026-10-27-r54-cpu-boundary/README.md)已显示这些分量可以分开记录,但其活动水平不支持成本验收;旧hosted第24位置的原因仍未知。 + +## 固定执行 + +以 PR #106 的 `beeb64496b2e2898e8619fa5e9c39f490a3891c4` 为基点。入口为[工作流](../../.github/workflows/r54-cpu.yml),只接受下述三个明确分支的创建push,拒绝attempt2;后续代码/导出器/证据修正不自动触发替代观察。原 `r54-cost.yml`、两批各112位置、原5%护栏及其失败记录不变。 + +首个`perf/grapheme-cpu-hosted-r54`创建作业 [40509157581](https://github.com/JavaZeroo/termforge/actions/runs/40509157581) 来自`eed0ab7bae5fcc5d05a25cea5161dabc4f935878`,attempt1,在31项准备测试中的发布夹具路径边界比较处失败。夹具根路径未规范化,被测发布器则将输入路径resolve后比较;旧job没有记录该临时目录的实际路径形式。构建、诊断和导出均未启动,实际观察为0,旧run不重跑。本地使用真实8.3别名复现了该缺陷:旧比较为false、规范化后为true;这不是旧hosted路径形式的直接证据。修正仅将夹具根目录规范化,不放宽发布器路径边界。 + +修正与审查后,`perf/grapheme-cpu-hosted-r54-setup2`于2026-10-27 13:22:55 UTC在`c14adb9a7fa446476ad5c95c1f8c1b139ceb5ac4`创建。提供者列表只出现无路径过滤的[默认回归40511533857](https://github.com/JavaZeroo/termforge/actions/runs/40511533857),没有该分支的CPU或候选工作流作业,实际诊断观察仍为0;原实验分支同源码更新对应的CPU作业40511437233为skipped。不能将默认回归计作诊断执行。 + +GitHub的[路径过滤规则](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#git-diff-comparisons)依赖push文件差异,空差异不运行工作流。上述缺席与新分支在已发布commit上创建、两个工作流均有路径过滤相符,但事件实际文件列表未留存。为消除入口对此差异计算的依赖,本诊断移除paths过滤,仍保留明确分支白名单、创建push条件和attempt1检查。 + +第三入口预声明为`perf/grapheme-cpu-hosted-r54-entry3`的新创建push,使用修订后新source/run身份,一次完成原构建及六位置诊断。前三个入口的实际状态分别保留,不重建旧分支、不重跑旧run、不增加或替换任何已存在的测量样本;后续修订push仍不会执行诊断。该入口变化不改测量代码、oracle、停止条件或产品容差。[实际run40512310499及归档复核](results/2026-10-27-r54-cpu-hosted/README.md)已完成:六个位置输出正确、计量完整,扣除自身完整工作后仍有3.333%–5.263%的余额;没有成本或性能验收结论。 + +一个新的`windows-2025` VM依次准备Python3.12、psutil7.2.2、Rust1.92.0,执行合同测试,然后[复建并审计](r54_build.py)原固定 `.3/.4` 及四次短正确性。仍是MSVC x64、Release、locked/offline、no-default-features、jobs1、独立target及两个共同harness覆盖;80/84来源文件、原锁文件、真实编译/链接日志和EXE哈希保持可追溯。它是新构建,不借用本地或上次hosted二进制身份。 + +构建/测试结束后只执行一次 `.3,.4,.4,.3,.3,.4`,全部为原interactive 22,000B、chunk1、observe0、内部CPU/墙钟至少2秒。使用未经修改的`r54_cpu_diagnostic`完整子进程/runner/全机计量,原完整输出oracle和来源前后审计继续执行。单子进程90秒、清理5秒;来源、输出、计数或退出不成立时立即停止,不补样。构建期限600秒、job30分钟;构建失败不进入诊断。 + +本次不跑空闲资格预检,不计算A/B成本比或吞吐回归,不接受或调整产品性能容差。`accounting_complete`只表示六份诊断记录完整。全机余额保留正负,可能包含其他guest活动、计数粒度和采样边界;不把它称为某个外部进程的精确CPU,也不能补造旧hosted缺失的全生命周期计数。两台宿主的绝对数值不能直接用于性能比较。 + +## 证据和公开范围 + +保留GitHub事件/run/attempt/commit、实际runner/映像、构建来源、host OS/CPU、原始计数及全部观察;分析时从提供者作业元数据独立核对身份。没有修改电源、优先级、亲和性或后台服务的步骤。物理宿主排他性与频率仍未知。 + +发布器的显式`cpu-boundary`模式只包含本次构建和六位置诊断的固定白名单,与原成本批次分开。它在任何staging之前核对本次raw schema/run/attempt/source,失败和不完整流也保留;测量、sanity及诊断入口identity的stdout/stderr字节不改;bundle内的构建identity日志保留完整内容并执行路径前缀替换。元数据中的实际长/短/转义路径改为别名,子PID及进程清单不公开;清单同时列原始/公开SHA和大小。绝不递归上传checkout、Cargo缓存或任意目录。新分支身份和schema不会被原默认发布模式接受。 + +只有下载并审核实际产物、源码/构建、全部原字节oracle和CPU等式之后,才将观察写入 #54。若此诊断支持改进后续环境计量,须另行冻结新协议与完整样本计划;不倒改原v1,也不以诊断完成代替受控A/B、allocator/projection及组件归因、预算提案和用户确认。 diff --git a/experiments/measure/R54-HOSTED.md b/experiments/measure/R54-HOSTED.md new file mode 100644 index 00000000..ee25c865 --- /dev/null +++ b/experiments/measure/R54-HOSTED.md @@ -0,0 +1,40 @@ +# R54:独立 Windows 虚拟机上的固定批次 + +本流程接续 [#54](https://github.com/JavaZeroo/termforge/issues/54) 和 [v1 合同](R54-COST.md),不改变源码、负载、两批各112个位置、计时端点、统计或预算。主工作站已有一次活动预检拒绝;不在同一宿主循环等绿,改在一个新的 GitHub-hosted `windows-2025` 虚拟机上建立完整来源和环境记录。它是本次 headless 核心成本研究的环境,不代替 Windows 11 x64 产品或 WindTerm 性能验收。 + +## 运行与停止 + +[工作流](../../.github/workflows/r54-cost.yml)只在实验分支 `perf/grapheme-hosted-r54` 的创建 push 事件执行成本作业,不在 PR 事件或后续源码/证据修正时自动补跑,也不改变 main 的默认构建。每次工作流用独立 run ID、attempt、源码和输出目录;拒绝对旧 workflow run 的 attempt 2 及后续重跑。首次运行后,新的调查需先明确假设、范围和新身份,再审查执行入口,不能由导出器修正偷偷触发替代样本。 + +同一虚拟机依次完成固定 Python/Rust、纯合同测试、两份来源构建、四次短正确性检查,再运行原两个种子。构建和测试结束后才开始成本预检与计时。没有自动恢复、补样、重试或放宽5%环境诊断护栏的入口;第一批失败即保留当前位置,第二批不启动。两批成功也只写 `complete_diagnostic`,`performance_gate=not_evaluated`。 + +同一任务的新 `CARGO_HOME` 用于获取锁定依赖;两个编译目标目录独立。脚本拒绝未知编译环境覆盖与 Cargo 配置文件,只覆盖原 `.3/.4` 的 Cargo.toml/src/measure.rs。每份仍使用 Rust1.92.0、MSVC x64、Release、locked/offline、no-default-features和jobs=1。完整源码前后哈希、真实命令/日志、实际linker及EXE、编译模式和四次原状态摘要由 [builder](r54_build.py) 留证;由原 [输入校验器](r54_inputs.py)重新读取验证之后才进入测量。 + +构建/准备调用各有期限,构建为600秒、短正确性调用为10秒;这些不是产品性能门槛。构建超时终止直接子进程并阻止全部测量,不据此声称已回收其派生进程;虚拟机生命周期由CI提供者管理。实际成本仍使用原内部60秒/子进程90秒期限,整个job30分钟。正常构建返回后才允许成本入口,不能将超时残留视作空闲。 + +## 宿主限制 + +保留 GitHub事件、run ID/attempt、实际提交、runner类型、系统映像、OS build/UBR、CPU与电源策略。固定两个种子的同口径结果分别报告,不混入主工作站原构建或历史12样本。`hosted_identity` 只检查该流程需要的元数据;分析时还须从GitHub作业记录核对实际runner与源码身份,环境变量本身不是物理隔离证明。 + +GitHub runner服务、操作系统后台任务、虚拟机邻居、物理CPU频率和调度仍可能影响结果。原5%筛选只描述guest可观察CPU活动;它不是用户确认的数值性能容差,也不证明物理宿主独占。出现噪声、失败或两批不一致时保留全部观察,再提出下一项可检验的调查。 + +## 发布边界 + +[发布器](r54_publish.py)只接受本流程的真实GitHub-hosted首次作业,并使用固定文件白名单。它不递归上传工作目录、checkout、编译缓存或个人文件。仅保存本次公开源码/合成输入产生的结果;完整进程名称/PID记录与未知环境字段不导出,工作区、runner profile和临时路径前缀改为别名。没有读取完整进程命令行或任意环境变量的步骤。 + +Windows 长路径及实际8.3别名均纳入替换,兼容大小写、正斜杠和编译器转义形式;通过[GetShortPathNameW](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew)查询,失败则不发布,不能猜测短名。首次作业导出曾漏掉两份构建日志中的短profile前缀;原包保留,审阅后的公开副本另列哈希及转换,不回填为旧运行使用了修正版导出器。 + +两个核心的成本及短正确性 stdout/stderr 按原字节保留,包括失败或无效编码;其生产者只接收公开固定合成负载。完整构建日志保留内容并替换宿主路径前缀。JSON/JSONL记录只做上述元数据筛选和路径处理,不改变时间、工作量、状态摘要、顺序或失败状态。中断留下的JSONL半行不修补、不删除,保留全部行和解析失败位置,独立样本原始流仍导出。`publication.json`列出每份原文件和发布文件的SHA、大小及处理方式。导出前核对原始 `hosted.json` 的run ID/attempt/SHA;空目录或另一次作业不生成成功包。发布目录在所有文件完成后才生效,上传仅指向该目录;未通过检查的半成品目录不会上传。 + +原始完整进程身份只在临时虚拟机内保留,公开结果保留全机CPU原计数、全部成本原始流及其来源。不能用未导出的逐进程字段做外部归因声明;后续若需要这种归因,另行预声明可公开的受控观测。所有CI结果须下载复核其清单、原始样本和统计后才能写入 #54 的成本结论。 + +## 手动入口(只供同一受控作业) + +```powershell +python -m experiments.measure.r54_hosted --out local/r54-raw +python -m experiments.measure.r54_publish --raw local/r54-raw --out local/r54-public +``` + +本机不得伪造GitHub环境变量运行该流程。普通本地准备可使用 `r54_build --out <新目录>`,要求实际Windows、已提交的干净源码及显式任务缓存;本轮不重复已有主工作站成本预检。测试只验证既有构建输入/产物边界,正式成本样本来自上述实际作业。 + +首次真实作业的来源、全部原始产物、环境护栏停止位置和独立复核见[固定记录](results/2026-10-27-r54-hosted/README.md)。这是不完整批次,没有配对成本摘要或性能通过结论。 diff --git a/experiments/measure/r54_batch.py b/experiments/measure/r54_batch.py new file mode 100644 index 00000000..65f8523a --- /dev/null +++ b/experiments/measure/r54_batch.py @@ -0,0 +1,261 @@ +"""Execute the frozen R54 sequence without retries or a product speed verdict.""" +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import hashlib +import json +import os +from pathlib import Path +import statistics +import subprocess +import tempfile + +from experiments.measure.r54_contract import CHUNKS, CPU_SECONDS, SEEDS, SOURCES, sample_order, validate_sample + + +def utc(): + return datetime.now(timezone.utc).isoformat() + + +def sha(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + + +def write_json(path, value): + """Replace only a live manifest; frozen inputs and stream files use create-only.""" + # A previous interrupted write may leave a partial temporary file. Give + # every attempt its own name so final failure/interruption recording still + # works; leave incomplete files for diagnosis rather than overwriting them. + with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', dir=path.parent, + prefix=path.name + '.', suffix='.tmp', delete=False) as stream: + temporary = Path(stream.name) + json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False) + stream.write('\n') + stream.flush() + os.fsync(stream.fileno()) + temporary.replace(path) + + +def capture(command, output, timeout=90): + """Direct-child streams stay byte-exact even on invalid UTF-8 or timeout.""" + result = dict(command=command, started_utc=utc(), exit_code=None, timed_out=False) + with (output / 'stdout.bin').open('xb') as stdout, (output / 'stderr.bin').open('xb') as stderr: + try: + result['exit_code'] = subprocess.run(command, stdout=stdout, stderr=stderr, + stdin=subprocess.DEVNULL, timeout=timeout).returncode + except subprocess.TimeoutExpired: + result.update(timed_out=True, error=f'direct child timed out after {timeout}s') + except OSError as error: + result['error'] = str(error) + result['finished_utc'] = utc() + result['streams'] = {name: dict(bytes=(output / name).stat().st_size, sha256=sha(output / name)) + for name in ('stdout.bin', 'stderr.bin')} + return result + + +def run_schedule(commands, seed, output, context): + """Process/artifact seam. Provenance and environment checks belong to the CLI. + + samples_complete means only the ordered process results validated. It never + establishes host isolation, physical CPU exclusivity or performance parity. + """ + if seed not in SEEDS or set(commands) != set(SOURCES): + raise ValueError('unknown seed or source pair') + output = Path(output) + output.mkdir(parents=True, exist_ok=False) + write_json(output / 'context.json', context) + manifest = dict(schema='termforge-r54-batch-v1', seed=seed, status='running', + started_utc=utc(), attempted=0, valid_samples=0, + active_sequence=None, performance_gate='not_evaluated', + context_sha256=sha(output / 'context.json')) + records = [] + try: + with (output / 'samples.jsonl').open('x', encoding='utf-8') as samples: + for item in sample_order(seed): + directory = output / f"{item['sequence']:03d}" + directory.mkdir() + command = list(commands[item['variant']]) + [item['route'], str(item['chunk']), '0', str(CPU_SECONDS)] + record = dict(item=item, status='started', command=command) + manifest.update(active_sequence=item['sequence'], attempted=manifest['attempted'] + 1) + write_json(output / 'manifest.json', manifest) + write_json(directory / 'record.json', record) + try: + monitored = context.get('environment_policy') is not None + if monitored: + from experiments.measure.r54_environment import POLICY, cpu_delta, cpu_snapshot + expected_sha = context['builds'][item['variant']]['binary']['sha256'] + if sha(command[0]) != expected_sha: + raise ValueError('binary changed before sample') + before = cpu_snapshot() + record.update(capture(command, directory)) + if monitored: + after = cpu_snapshot() + record['environment'] = dict(cpu_delta(before, after), before=before, after=after) + if record['timed_out'] or record['exit_code'] != 0: + raise ValueError(record.get('error', 'nonzero child exit')) + result = json.loads((directory / 'stdout.bin').read_bytes().decode('utf-8')) + validate_sample(result, item) + record['result'] = result + if monitored: + if sha(command[0]) != expected_sha or result['cpu_clock'] != 'GetProcessTimes kernel plus user': + raise ValueError('runtime binary or clock identity changed') + env = record['environment'] + # The outer host interval includes preparation/oracle and + # runner work excluded by the inner measured CPU interval. + # Retain this conservative estimate; never subtract it from + # costs or claim an exact per-process background partition. + remaining = env['busy_seconds'] - result['interval_cpu_seconds'] + env['unattributed_busy_percent_upper_estimate'] = remaining * 100 / env['total_seconds'] + if remaining < 0 or env['unattributed_busy_percent_upper_estimate'] > POLICY['max_unattributed_busy_percent']: + raise ValueError('host activity guard failed; sample retained, batch stopped') + record['status'] = 'valid' + except Exception as error: + record.update(status='failed', error=f'{type(error).__name__}: {error}') + write_json(directory / 'record.json', record) + samples.write(json.dumps(record, ensure_ascii=False, allow_nan=False) + '\n') + samples.flush() + os.fsync(samples.fileno()) + records.append(record) + if record['status'] != 'valid': + raise ValueError(f"sample {item['sequence']} failed: {record['error']}") + manifest['valid_samples'] += 1 + write_json(output / 'summary.json', summarize(records, seed)) + manifest.update(status='samples_complete', active_sequence=None) + except Exception as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + except BaseException: + manifest['status'] = 'interrupted' + raise + finally: + manifest['finished_utc'] = utc() + write_json(output / 'manifest.json', manifest) + return manifest + + +def distribution(values): + median = statistics.median(values) + return dict(count=len(values), median=median, min=min(values), max=max(values), + mad=statistics.median(abs(value - median) for value in values)) + + +def summarize(records, seed): + """Require the whole ordered batch before computing within-repeat ratios.""" + if seed not in SEEDS or len(records) != len(sample_order(seed)): + raise ValueError('incomplete or unknown batch') + for record, expected in zip(records, sample_order(seed)): + if record['item'] != expected or record['status'] != 'valid': + raise ValueError('failed or reordered sample') + validate_sample(record['result'], expected) + groups = [] + for route, chunks in CHUNKS.items(): + for chunk in chunks: + pairs = {} + samples = {variant: dict(cpu_ns_per_byte=[], wall_ns_per_byte=[]) for variant in ('baseline', 'candidate')} + for record in records: + item, result = record['item'], record['result'] + if (item['route'], item['chunk']) != (route, chunk): + continue + volume = result['rounds'] * result['bytes_per_round'] + cpu = result['interval_cpu_seconds'] * 1e9 / volume + wall = result['interval_wall_seconds'] * 1e9 / volume + samples[item['variant']]['cpu_ns_per_byte'].append(cpu) + samples[item['variant']]['wall_ns_per_byte'].append(wall) + pairs.setdefault(item['repeat'], {})[item['variant']] = cpu + ratios = [pair['candidate'] / pair['baseline'] for _, pair in sorted(pairs.items())] + groups.append(dict(route=route, chunk=chunk, paired_cpu_ratios=ratios, + paired_cpu_ratio_median=statistics.median(ratios), + samples={variant: {metric: dict(distribution(values), values=values) + for metric, values in metrics.items()} + for variant, metrics in samples.items()})) + return dict(seed=seed, groups=groups, performance_gate='not_evaluated', + budget_status='not_set', receive_seconds=None, + input_latency_seconds=None, presented_done=None) + + +def run(plan_path, bundle, declaration_path, output, seed): + """Validate provenance and observe the host before any cost workload runs.""" + from experiments.measure.r54_environment import POLICY, preflight + from experiments.measure.r54_inputs import IDENTITY, ROOT, git, load_builds, read, require + if os.name != 'nt': + raise ValueError('v1 build binding and environment observation require Windows') + output = Path(output) + output.mkdir(parents=True, exist_ok=False) + manifest = dict(schema='termforge-r54-execution-v1', status='preparing', seed=seed, + started_utc=utc(), attempted=0, valid_samples=0, performance_gate='not_evaluated') + write_json(output / 'manifest.json', manifest) + try: + runner = dict(commit=git(ROOT, 'rev-parse', 'HEAD'), worktree_status=git(ROOT, 'status', '--porcelain'), + sources={name: sha(ROOT / name) for name in ( + 'experiments/measure/r54_batch.py', 'experiments/measure/r54_inputs.py', + 'experiments/measure/r54_environment.py', 'experiments/measure/r54_contract.py', + 'experiments/measure/run.py', 'experiments/measure/compare.py')}) + write_json(output / 'runner.json', runner) + manifest['runner_identity_sha256'] = sha(output / 'runner.json') + write_json(output / 'manifest.json', manifest) + require(seed in SEEDS, 'unknown batch seed') + require(not runner['worktree_status'], 'runner source must be committed and clean') + binding = load_builds(plan_path, bundle) + inputs = output / 'inputs' + inputs.mkdir() + (inputs / 'plan.json').write_bytes(Path(plan_path).read_bytes()) + for name, digest in binding['evidence'].items(): + payload = (Path(bundle) / name).read_bytes() + require(hashlib.sha256(payload).hexdigest() == digest, 'build evidence changed while copying') + (inputs / name).write_bytes(payload) + write_json(inputs / 'binding.json', binding) + for variant, command in binding['commands'].items(): + directory = output / (variant + '-identity') + directory.mkdir() + result = capture(command + ['--identity'], directory, timeout=10) + write_json(directory / 'record.json', result) + require(result['exit_code'] == 0 and read(directory / 'stdout.bin') == IDENTITY, + 'actual compiled identity mismatch') + environment = preflight(output, read(declaration_path)) + require(environment['status'] == 'low_activity_observed', 'host preflight rejected; no cost samples launched') + context = dict(binding, evidence_kind='fixed_core_cost', environment_policy=POLICY, + environment_sha256=sha(output / 'environment.json'), runner_commit=runner['commit'], + runner_sources=runner['sources'], runner_identity_sha256=manifest['runner_identity_sha256']) + result = run_schedule(binding['commands'], seed, output / 'samples', context) + manifest.update(status=result['status'], attempted=result['attempted'], valid_samples=result['valid_samples']) + require(result['status'] == 'samples_complete', result.get('error', 'sample execution failed')) + require(load_builds(plan_path, bundle) == binding, 'build inputs changed during batch') + for name, digest in context['runner_sources'].items(): + require(sha(ROOT / name) == digest, 'runner changed during batch') + power = output / 'power-policy-after' + power.mkdir() + observed = capture(['powercfg', '/getactivescheme'], power, timeout=10) + write_json(power / 'record.json', observed) + require(observed['exit_code'] == 0 and (power / 'stdout.bin').read_bytes() == + (output / 'power-policy/stdout.bin').read_bytes(), 'power policy changed during batch') + manifest['status'] = 'complete_diagnostic' + except Exception as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + except BaseException: + manifest['status'] = 'interrupted' + raise + finally: + samples_manifest = output / 'samples/manifest.json' + if samples_manifest.exists(): + recorded = read(samples_manifest) + manifest.update(attempted=recorded['attempted'], valid_samples=recorded['valid_samples']) + manifest['finished_utc'] = utc() + write_json(output / 'manifest.json', manifest) + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--plan', type=Path, required=True) + parser.add_argument('--build-bundle', type=Path, required=True) + parser.add_argument('--environment', type=Path, required=True) + parser.add_argument('--out', type=Path, required=True) + parser.add_argument('--seed', type=int, choices=SEEDS, required=True) + args = parser.parse_args() + result = run(args.plan, args.build_bundle, args.environment, args.out, args.seed) + print(json.dumps(result, ensure_ascii=False)) + raise SystemExit(0 if result['status'] == 'complete_diagnostic' else 1) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/r54_build.py b/experiments/measure/r54_build.py new file mode 100644 index 00000000..21fc4c8f --- /dev/null +++ b/experiments/measure/r54_build.py @@ -0,0 +1,196 @@ +"""Create the audited R54 Windows build bundle before any cost measurements.""" +import argparse +import json +import os +from pathlib import Path +import re +import subprocess + +from experiments.measure.r54_batch import capture, sha, utc, write_json +from experiments.measure.r54_contract import ROOT, SOURCES, expected_sample, prepare +from experiments.measure.r54_inputs import IDENTITY, OVERLAYS, git, load_builds, require +from experiments.measure.run import validate_result + +OVERRIDES = ('RUSTFLAGS', 'CARGO_ENCODED_RUSTFLAGS', 'CARGO_BUILD_TARGET', 'CARGO_BUILD_RUSTFLAGS', + 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS', 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER', + 'RUSTC', 'RUSTC_WRAPPER', 'RUSTC_WORKSPACE_WRAPPER') +RECORDED_ENVIRONMENT = OVERRIDES + ('CARGO_HOME', 'RUSTUP_HOME', 'RUSTUP_TOOLCHAIN', 'CARGO_TERM_COLOR', 'LIB', 'INCLUDE') + + +def run_logged(command, directory, prefix, *, cwd=ROOT, environment=None, timeout=300): + """Build/setup logs are byte-exact; failure prevents entering the cost runner.""" + result = dict(command=command, started_utc=utc(), exit_code=None, timed_out=False) + stdout, stderr = directory / (prefix + '.stdout'), directory / (prefix + '.stderr') + try: + with stdout.open('xb') as output, stderr.open('xb') as error: + try: + process = subprocess.run(command, cwd=cwd, env=environment, stdin=subprocess.DEVNULL, + stdout=output, stderr=error, timeout=timeout) + result['exit_code'] = process.returncode + except subprocess.TimeoutExpired: + result.update(timed_out=True, error='build/setup direct-child timeout; no process-tree cleanup claim') + require(result['exit_code'] == 0, f'{prefix} failed; preserve logs, no automatic retry') + except BaseException as error: + result['error'] = f'{type(error).__name__}: {error}' + raise + finally: + result['finished_utc'] = utc() + result['logs'] = {path.name: dict(bytes=path.stat().st_size, sha256=sha(path)) + for path in (stdout, stderr) if path.exists()} + write_json(directory / (prefix + '.process.json'), result) + return result + + +def reject_hidden_configuration(checkout, environment): + # Cargo searches the working directory's ancestors and CARGO_HOME. A clean, + # explicit cache prevents inherited config from changing the fixed flags. + cargo_home = environment.get('CARGO_HOME') + require(bool(cargo_home), 'CARGO_HOME must name an explicit task-owned cache') + locations = [checkout, *checkout.parents, Path(cargo_home)] + for location in locations: + config_dir = location if location == Path(cargo_home) else location / '.cargo' + require(not any((config_dir / name).exists() for name in ('config', 'config.toml')), + 'unreviewed Cargo configuration file') + + +def prepare_builds(output): + output = Path(output).resolve() + output.mkdir(parents=True, exist_ok=False) + status = dict(schema='termforge-r54-build-preparation-v1', status='preparing', started_utc=utc(), + source=git(ROOT, 'rev-parse', 'HEAD'), runner_sha256=sha(Path(__file__)), + formal_cost_samples=0, performance_gate='not_evaluated') + write_json(output / 'preparation.json', status) + try: + environment = {name: os.environ.get(name) for name in RECORDED_ENVIRONMENT} + for name in OVERRIDES: + require(environment[name] in (None, ''), f'unreviewed compiler override: {name}') + for name, value in os.environ.items(): + if value and name.startswith(('CARGO_', 'RUST')) and name not in RECORDED_ENVIRONMENT: + raise ValueError(f'unreviewed compiler environment name: {name}') + require(os.name == 'nt', 'the frozen build pair targets Windows x64') + require(not git(ROOT, 'status', '--porcelain'), 'prepare from a committed, clean source tree') + frozen = prepare(output / 'plan-v1.json') + builds = {} + for variant, source in SOURCES.items(): + checkout = output / ('source-' + variant) + run_logged(['git', 'worktree', 'add', '--detach', str(checkout), source['commit']], + output, variant + '-checkout', timeout=60) + require(git(checkout, 'rev-parse', 'HEAD') == source['commit'] and + not git(checkout, 'status', '--porcelain'), 'checkout identity or cleanliness mismatch') + reject_hidden_configuration(checkout, environment) + files = git(checkout, 'ls-files', 'experiments/candidates/alacritty').splitlines() + before = {name: sha(checkout / name) for name in files} + for name in OVERLAYS: + (checkout / name).write_bytes((ROOT / name).read_bytes()) + after = {name: sha(checkout / name) for name in files} + changed = git(checkout, 'diff', '--name-only').splitlines() + require(set(changed) == set(OVERLAYS), 'unexpected source changes during overlay') + require(all(before[name] == after[name] for name in files if name not in OVERLAYS), + 'original lockfile, adapter or vendor changed') + patch = output / (variant + '-harness.patch') + patch.write_bytes(subprocess.check_output(['git', 'diff', '--', *OVERLAYS], cwd=checkout, timeout=30)) + target = output / ('target-' + variant + '-unwrapped') + require(not target.exists(), 'target directory already exists') + command = ['cargo', '+1.92.0', 'build', '--manifest-path', + str(checkout / 'experiments/candidates/alacritty/Cargo.toml'), + '--release', '--locked', '--offline', '--no-default-features', '--jobs', '1', + '--bin', 'alacritty-measure', '--target', 'x86_64-pc-windows-msvc', + '--target-dir', str(target), '-vv'] + builds[variant] = dict(commit=source['commit'], tree=git(checkout, 'rev-parse', 'HEAD^{tree}'), + candidate_tree=git(checkout, 'rev-parse', 'HEAD:experiments/candidates/alacritty'), + version=source['version'], overlay_files=list(OVERLAYS), changed_paths=changed, + before_files=before, after_files=after, overlay_patch_sha256=sha(patch), + command=command, target_dir=str(target), status='not_built') + plan = dict(schema='termforge-r54-local-build-plan-v1', harness_source=status['source'], + protocol_sha256=frozen['protocol_sha256'], frozen_plan=dict(sha256=sha(output / 'plan-v1.json')), + rustc_vv=subprocess.check_output(['rustc', '+1.92.0', '-Vv'], timeout=30).decode('utf-8'), + cargo_vv=subprocess.check_output(['cargo', '+1.92.0', '-Vv'], timeout=30).decode('utf-8'), + environment=environment, additional_build_environment={'RUSTC_LOG': 'rustc_codegen_ssa::back::link=info'}, + builds=builds, formal_samples=0, performance_gate='not_evaluated') + write_json(output / 'build-plan.json', plan) + for variant, spec in builds.items(): + checkout = output / ('source-' + variant) + run_logged(['cargo', '+1.92.0', 'fetch', '--locked', '--manifest-path', + str(checkout / 'experiments/candidates/alacritty/Cargo.toml'), + '--target', 'x86_64-pc-windows-msvc'], output, variant + '-fetch', cwd=checkout) + receipt = dict(variant=variant, build_plan_sha256=sha(output / 'build-plan.json'), + command=spec['command'], status='running', formal_samples=0, performance_gate='not_evaluated') + write_json(output / (variant + '-build.json'), receipt) + try: + require(all(sha(checkout / name) == digest for name, digest in spec['after_files'].items()), + 'fetch changed fixed build inputs') + receipt.update(run_logged(spec['command'], output, variant + '-build', cwd=checkout, + environment=dict(os.environ, **plan['additional_build_environment']), timeout=600)) + require(all(sha(checkout / name) == digest for name, digest in spec['after_files'].items()), + 'build changed fixed inputs') + binary = Path(spec['target_dir']) / 'x86_64-pc-windows-msvc/release/alacritty-measure.exe' + receipt['binary'] = dict(bytes=binary.stat().st_size, sha256=sha(binary)) + run_logged([str(binary), '--identity'], output, variant + '-identity', cwd=checkout, timeout=10) + receipt['identity'] = json.loads((output / (variant + '-identity.stdout')).read_bytes().decode('utf-8')) + require(receipt['identity'] == IDENTITY, 'compiled allocator mode mismatch') + receipt['status'] = 'built_identity_only' + except BaseException as error: + receipt.update(status='failed', error=f'{type(error).__name__}: {error}') + raise + finally: + write_json(output / (variant + '-build.json'), receipt) + audit(output, plan) + load_builds(output / 'plan-v1.json', output) + status['status'] = 'built_and_audited' + except BaseException as error: + status.update(status='failed', error=f'{type(error).__name__}: {error}') + raise + finally: + status['finished_utc'] = utc() + write_json(output / 'preparation.json', status) + return status + + +def audit(output, plan): + import tomllib + rustc = Path(subprocess.check_output(['rustup', 'which', '--toolchain', '1.92.0', 'rustc'], timeout=30).decode('utf-8').strip()) + report = dict(schema='termforge-r54-local-build-audit-v1', builds={}, short_sanity_calls=[], + compiler=dict(sha256=sha(rustc), rustc_vv=plan['rustc_vv']), passed=False, + formal_samples=0, performance_gate='not_evaluated', environment_idle='not_measured') + try: + for variant, spec in plan['builds'].items(): + receipt_path = output / (variant + '-build.json') + receipt = json.loads(receipt_path.read_text(encoding='utf-8')) + checkout = output / ('source-' + variant) + metadata = tomllib.loads((checkout / 'experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml').read_text(encoding='utf-8')) + require(metadata['package']['version'] == SOURCES[variant]['version'], 'vendor version mismatch') + log = (output / (variant + '-build.stderr')).read_text(encoding='utf-8') + names = {json.loads('"' + value + '"') for value in re.findall(r'invoking linker "([^\n]+?)"', log)} + require(len(names) == 1, 'build log must identify one actual linker') + linker = Path(next(iter(names))) + require(linker.name == 'link.exe' and linker.is_file(), 'actual MSVC linker missing') + report['builds'][variant] = dict(source=spec['commit'], version=metadata['package']['version'], + candidate_tree=spec['candidate_tree'], tracked_candidate_files=len(spec['after_files']), + changed_paths=spec['changed_paths'], binary=receipt['binary'], identity=receipt['identity'], + linker=dict(path=str(linker), sha256=sha(linker)), receipt_sha256=sha(receipt_path)) + binary = Path(spec['target_dir']) / 'x86_64-pc-windows-msvc/release/alacritty-measure.exe' + for route in ('project', 'interactive'): + directory = output / f'sanity-{variant}-{route}' + directory.mkdir() + response = capture([str(binary), route, '4096', '0', '0.1'], directory, timeout=10) + report['short_sanity_calls'].append(dict(variant=variant, route=route, passed=False, **response)) + require(response['exit_code'] == 0 and not response['timed_out'], 'short correctness call failed') + result = json.loads((directory / 'stdout.bin').read_bytes().decode('utf-8')) + validate_result(result, .1, expected_sample(dict(variant=variant, route=route, chunk=4096))) + report['short_sanity_calls'][-1]['passed'] = True + require(report['builds']['baseline']['linker'] == report['builds']['candidate']['linker'], 'source pair used different linkers') + report['passed'] = True + finally: + write_json(output / 'build-audit.json', report) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--out', type=Path, required=True) + args = parser.parse_args() + value = prepare_builds(args.out) + print(json.dumps(value)) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/r54_cpu_diagnostic.py b/experiments/measure/r54_cpu_diagnostic.py new file mode 100644 index 00000000..6b04b424 --- /dev/null +++ b/experiments/measure/r54_cpu_diagnostic.py @@ -0,0 +1,236 @@ +"""Separate whole-child CPU accounting from R54's unchanged v1 cost batches.""" +from __future__ import annotations + +import argparse +import ctypes +from ctypes import wintypes +import json +import math +import os +from pathlib import Path +import subprocess +import sys +import time + +from experiments.measure.r54_batch import capture, sha, utc, write_json +from experiments.measure.r54_contract import CPU_SECONDS, ROOT, SOURCES, validate_sample +from experiments.measure.r54_environment import cpu_delta, cpu_snapshot, host_identity +from experiments.measure.r54_inputs import IDENTITY, git, load_builds, read, require + +PROTOCOL = dict(schema='termforge-r54-cpu-boundary-v1', + order=['baseline', 'candidate', 'candidate', 'baseline', 'baseline', 'candidate'], + route='interactive', chunk=1, observe=0, inner_cpu_seconds=CPU_SECONDS, + process_timeout_seconds=90, cleanup_timeout_seconds=5, + activity_acceptance_gate=False, performance_gate='not_evaluated', + purpose='separate whole-child, inner interval and runner CPU; no cost ratios', + residual_scope='other guest activity plus counter quantization and bracket mismatch; not exact attribution') + + +def partition_cpu(record, inner_cpu): + """Expose balances without labelling the signed residual external activity. + + Counter updates and non-atomic bracket endpoints can leave a negative + residual. Keep it visible; do not clamp, normalize or subtract it from cost. + """ + total = record['environment']['total_seconds'] + busy = record['environment']['busy_seconds'] + child = record['child_cpu']['total_seconds'] + runner = record['runner_cpu_seconds'] + if (any(not math.isfinite(value) or value < 0 for value in (total, busy, child, runner, inner_cpu)) + or total <= 0 or child < inner_cpu): + raise ValueError('inconsistent whole-child or CPU accounting') + residual = busy - child - runner + return dict(inner_only_balance_seconds=busy - inner_cpu, + inner_only_balance_percent=(busy - inner_cpu) * 100 / total, + child_outside_inner_seconds=child - inner_cpu, + runner_cpu_seconds=runner, unassigned_balance_seconds=residual, + unassigned_balance_percent=residual * 100 / total) + + +def exited_child_cpu(process): + """Borrow CPython's retained Windows process handle after wait, before close. + + The handle belongs to Popen. Querying by PID after exit would lose the + lifetime or risk a reused identity; retaining this handle avoids that race. + This private CPython dependency is limited to the pinned diagnostic runtime. + """ + if process.returncode is None: + raise ValueError('child must have exited before lifetime accounting') + creation, end, kernel, user = (wintypes.FILETIME() for _ in range(4)) + function = ctypes.WinDLL('kernel32', use_last_error=True).GetProcessTimes + function.argtypes = [wintypes.HANDLE] + [ctypes.POINTER(wintypes.FILETIME)] * 4 + function.restype = wintypes.BOOL + if not function(int(process._handle), ctypes.byref(creation), ctypes.byref(end), + ctypes.byref(kernel), ctypes.byref(user)): + raise ctypes.WinError(ctypes.get_last_error()) + ticks = lambda value: (value.dwHighDateTime << 32) | value.dwLowDateTime + return dict(creation_filetime=ticks(creation), exit_filetime=ticks(end), + kernel_seconds=ticks(kernel) / 10_000_000, + user_seconds=ticks(user) / 10_000_000, + total_seconds=(ticks(kernel) + ticks(user)) / 10_000_000) + + +def capture_cpu(command, output, timeout=90): + """Capture a direct child and its complete CPU lifetime, including exit. + + The fixed measurement executables do not spawn children. This helper does + not claim descendant accounting or cleanup. Stream bytes are never decoded + or rewritten here, including failed and timed-out executions. + """ + if os.name != 'nt' or sys.implementation.name != 'cpython' or sys.version_info[:2] != (3, 12): + raise ValueError('CPU diagnostic requires Windows CPython 3.12') + output = Path(output) + result = dict(command=command, started_utc=utc(), exit_code=None, timed_out=False, + child_cpu=None, clock='GetProcessTimes kernel plus user', + runtime=sys.version, accounting_scope='direct child only') + with (output / 'stdout.bin').open('xb') as stdout, (output / 'stderr.bin').open('xb') as stderr: + before = cpu_snapshot() + runner_before = time.process_time() + try: + # Popen.__exit__ waits without a bound. Keep the object alive for + # accounting, but use explicit bounded waits on failure instead. + child = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=stdout, stderr=stderr) + result['child_pid'] = child.pid + try: + child.wait(timeout=timeout) + except subprocess.TimeoutExpired: + result.update(timed_out=True, error=f'direct child timed out after {timeout}s') + child.kill() + child.wait(timeout=PROTOCOL['cleanup_timeout_seconds']) + except BaseException: + child.kill() + child.wait(timeout=PROTOCOL['cleanup_timeout_seconds']) + raise + result['exit_code'] = child.returncode + result['child_cpu'] = exited_child_cpu(child) + except (OSError, subprocess.SubprocessError) as error: + result['error'] = f'{type(error).__name__}: {error}' + finally: + runner_after = time.process_time() + after = cpu_snapshot() + result['runner_cpu_seconds'] = runner_after - runner_before + result['runner_cpu_before_seconds'] = runner_before + result['runner_cpu_after_seconds'] = runner_after + result['environment'] = dict(cpu_delta(before, after), before=before, after=after) + result['finished_utc'] = utc() + result['streams'] = {name: dict(bytes=(output / name).stat().st_size, sha256=sha(output / name)) + for name in ('stdout.bin', 'stderr.bin')} + return result + + +def run_observations(commands, output, context): + """Process/artifact seam for the fixed six-observation diagnostic only.""" + require(set(commands) == set(SOURCES), 'fixed source pair required') + output = Path(output) + output.mkdir(parents=True, exist_ok=False) + write_json(output / 'context.json', context) + write_json(output / 'protocol.json', PROTOCOL) + manifest = dict(schema='termforge-r54-cpu-observations-v1', status='running', + started_utc=utc(), attempted=0, accounted_observations=0, + context_sha256=sha(output / 'context.json'), protocol_sha256=sha(output / 'protocol.json'), + performance_gate='not_evaluated') + try: + for sequence, variant in enumerate(PROTOCOL['order']): + item = dict(sequence=sequence, variant=variant, route=PROTOCOL['route'], chunk=PROTOCOL['chunk']) + directory = output / f'{sequence:03d}' + directory.mkdir() + record = dict(item=item, status='started') + manifest.update(attempted=sequence + 1, active_sequence=sequence) + write_json(output / 'manifest.json', manifest) + write_json(directory / 'record.json', record) + try: + command = list(commands[variant]) + [item['route'], str(item['chunk']), '0', str(CPU_SECONDS)] + before_sha = sha(command[0]) + if 'binding' in context: + require(before_sha == context['binding']['builds'][variant]['binary']['sha256'], + 'binary changed before observation') + record.update(capture_cpu(command, directory, PROTOCOL['process_timeout_seconds'])) + require(not record['timed_out'] and record['exit_code'] == 0 and 'error' not in record, + record.get('error', 'nonzero child exit')) + result = json.loads((directory / 'stdout.bin').read_bytes().decode('utf-8')) + validate_sample(result, item) + require(result['cpu_clock'] == 'GetProcessTimes kernel plus user', 'wrong child CPU clock') + require(sha(command[0]) == before_sha, 'binary changed during observation') + record['result'] = result + record['accounting'] = partition_cpu(record, result['interval_cpu_seconds']) + record['status'] = 'accounted' + except Exception as error: + record.update(status='failed', error=f'{type(error).__name__}: {error}') + finally: + write_json(directory / 'record.json', record) + require(record['status'] == 'accounted', record.get('error', 'observation incomplete')) + manifest['accounted_observations'] += 1 + manifest.update(status='accounting_complete', active_sequence=None) + except Exception as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + except BaseException: + manifest['status'] = 'interrupted' + raise + finally: + manifest['finished_utc'] = utc() + write_json(output / 'manifest.json', manifest) + return manifest + + +def execute(plan, bundle, declaration, output): + """Audit retained builds; do not rebuild or repeat an old preflight/batch.""" + require(os.name == 'nt', 'Windows accounting required') + output = Path(output) + output.mkdir(parents=True, exist_ok=False) + manifest = dict(schema='termforge-r54-cpu-diagnostic-v1', status='preparing', started_utc=utc(), + performance_gate='not_evaluated') + try: + runner = dict(commit=git(ROOT, 'rev-parse', 'HEAD'), worktree_status=git(ROOT, 'status', '--porcelain'), + sources={name: sha(ROOT / name) for name in ( + 'experiments/measure/r54_cpu_diagnostic.py', 'experiments/measure/r54_environment.py', + 'experiments/measure/r54_batch.py', 'experiments/measure/r54_contract.py', + 'experiments/measure/r54_inputs.py', 'experiments/measure/run.py')}) + write_json(output / 'runner.json', runner) + require(not runner['worktree_status'], 'runner source must be committed and clean') + environment = read(declaration) + require(environment.get('host_kind') in ('shared_workstation', 'hosted_vm', 'dedicated_host') and + isinstance(environment.get('known_concurrent_work'), list), 'explicit environment declaration required') + write_json(output / 'environment.json', dict(host=host_identity(), declaration=environment, + scope='accounting diagnosis on declared host; no idle qualification or cost acceptance')) + binding = load_builds(plan, bundle) + write_json(output / 'binding.json', binding) + (output / 'plan-v1.json').write_bytes(Path(plan).read_bytes()) + for variant, command in binding['commands'].items(): + directory = output / (variant + '-identity') + directory.mkdir() + value = capture(command + ['--identity'], directory, timeout=10) + write_json(directory / 'record.json', value) + require(value['exit_code'] == 0 and read(directory / 'stdout.bin') == IDENTITY, 'actual build identity mismatch') + result = run_observations(binding['commands'], output / 'observations', + dict(evidence_kind='cpu_boundary_diagnostic', binding=binding, runner=runner)) + manifest['observations'] = result + require(result['status'] == 'accounting_complete', 'diagnostic stopped; no replacement observations') + require(load_builds(plan, bundle) == binding, 'build binding changed during diagnosis') + for name, digest in runner['sources'].items(): + require(sha(ROOT / name) == digest, 'runner changed during diagnosis') + manifest['status'] = 'accounting_complete' + except Exception as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + except BaseException: + manifest['status'] = 'interrupted' + raise + finally: + manifest['finished_utc'] = utc() + write_json(output / 'manifest.json', manifest) + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--plan', type=Path, required=True) + parser.add_argument('--build-bundle', type=Path, required=True) + parser.add_argument('--environment', type=Path, required=True) + parser.add_argument('--out', type=Path, required=True) + args = parser.parse_args() + result = execute(args.plan, args.build_bundle, args.environment, args.out) + print(json.dumps(result)) + raise SystemExit(0 if result['status'] == 'accounting_complete' else 1) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/r54_cpu_hosted.py b/experiments/measure/r54_cpu_hosted.py new file mode 100644 index 00000000..f523fad5 --- /dev/null +++ b/experiments/measure/r54_cpu_hosted.py @@ -0,0 +1,55 @@ +"""One fresh hosted build followed by the separately declared CPU-boundary diagnosis.""" +import argparse +import json +from pathlib import Path + +from experiments.measure.r54_batch import sha, utc, write_json +from experiments.measure.r54_build import prepare_builds +from experiments.measure.r54_contract import ROOT +from experiments.measure.r54_cpu_diagnostic import execute as diagnose +from experiments.measure.r54_hosted import CPU_BOUNDARY_BRANCHES, hosted_identity +from experiments.measure.r54_inputs import git, require + +def execute(output): + output = Path(output).resolve() + require(output.is_relative_to(ROOT / 'local'), 'hosted artifacts must stay under the workspace local directory') + output.mkdir(parents=True, exist_ok=False) + manifest = dict(schema='termforge-r54-cpu-hosted-v1', status='preparing', started_utc=utc(), + source=git(ROOT, 'rev-parse', 'HEAD'), runner_sha256=sha(Path(__file__)), + workflow_sha256=sha(ROOT / '.github/workflows/r54-cpu.yml'), + performance_gate='not_evaluated', diagnostic=None) + try: + manifest['hosted_identity'] = hosted_identity(CPU_BOUNDARY_BRANCHES) + write_json(output / 'hosted.json', manifest) + prepare_builds(output / 'bundle') + declaration = dict(host_kind='hosted_vm', no_known_concurrent_build_or_test=True, + known_concurrent_work=['This job completed setup, tests and both audited builds before diagnosis.', + 'GitHub runner service and operating-system background services remain.', + 'Physical host scheduling and neighboring virtual machines are unobserved.'], + purpose='One six-observation CPU-boundary diagnostic; no idle qualification or cost acceptance.') + write_json(output / 'environment-declaration.json', declaration) + manifest['status'] = 'diagnosing' + write_json(output / 'hosted.json', manifest) + result = diagnose(output / 'bundle/plan-v1.json', output / 'bundle', + output / 'environment-declaration.json', output / 'diagnostic') + manifest['diagnostic'] = result + require(result['status'] == 'accounting_complete', 'diagnosis stopped; no replacement observations') + manifest['status'] = 'accounting_complete' + except BaseException as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + raise + finally: + manifest['finished_utc'] = utc() + write_json(output / 'hosted.json', manifest) + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--out', type=Path, required=True) + args = parser.parse_args() + print(json.dumps(execute(args.out))) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/r54_environment.py b/experiments/measure/r54_environment.py new file mode 100644 index 00000000..49802986 --- /dev/null +++ b/experiments/measure/r54_environment.py @@ -0,0 +1,103 @@ +"""Read-only Windows activity evidence, with a predeclared diagnostic host guard.""" +import ctypes +from ctypes import wintypes +import math +import os +import platform +import time + +from experiments.measure.r54_batch import capture, utc, write_json + +POLICY = dict(schema='termforge-r54-environment-guard-v1', idle_intervals=10, + idle_interval_seconds=1, max_busy_percent=5.0, + max_unattributed_busy_percent=5.0, product_performance_budget=False, + measurement='GetSystemTimes; user + kernel - idle; all logical CPUs, at most 64', + scope='guest-visible CPU activity only; physical host contention and frequency stability remain unknown') + + +def cpu_snapshot(): + import psutil + if os.name != 'nt' or not 0 < (psutil.cpu_count() or 0) <= 64: + raise ValueError('this host guard supports Windows with 1..64 logical CPUs only') + idle, kernel, user = wintypes.FILETIME(), wintypes.FILETIME(), wintypes.FILETIME() + function = ctypes.WinDLL('kernel32', use_last_error=True).GetSystemTimes + function.argtypes = [ctypes.POINTER(wintypes.FILETIME)] * 3 + function.restype = wintypes.BOOL + if not function(ctypes.byref(idle), ctypes.byref(kernel), ctypes.byref(user)): + raise ctypes.WinError(ctypes.get_last_error()) + seconds = lambda value: ((value.dwHighDateTime << 32) | value.dwLowDateTime) / 10_000_000 + return dict(user=seconds(user), kernel_including_idle=seconds(kernel), idle=seconds(idle), + system=seconds(kernel) - seconds(idle), perf_counter_seconds=time.perf_counter(), utc=utc()) + + +def cpu_delta(before, after): + deltas = {name: after[name] - before[name] for name in ('user', 'system', 'idle')} + if any(not math.isfinite(value) or value < 0 for value in deltas.values()) or sum(deltas.values()) <= 0: + raise ValueError('invalid host CPU interval') + total, busy = sum(deltas.values()), deltas['user'] + deltas['system'] + return dict(total_seconds=total, busy_seconds=busy, busy_percent=busy * 100 / total) + + +def qualify_idle(intervals): + return (len(intervals) == POLICY['idle_intervals'] and + all(math.isfinite(item['busy_percent']) and 0 <= item['busy_percent'] <= POLICY['max_busy_percent'] + for item in intervals)) + + +def process_snapshot(): + """Private evidence contains names/CPU identities, never command lines or env.""" + import psutil + records = [] + for process in psutil.process_iter(['pid', 'name', 'create_time', 'cpu_times'], ad_value=None): + row = dict(process.info) + if row['cpu_times'] is not None: + row['cpu_times'] = row['cpu_times']._asdict() + records.append(row) + return records + + +def host_identity(): + import psutil + import winreg + def registry(key, names): + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key) as handle: + return {name: winreg.QueryValueEx(handle, name)[0] for name in names} + return dict(platform=platform.platform(), python=platform.python_version(), psutil=psutil.__version__, + cpu_logical=psutil.cpu_count(), cpu_physical=psutil.cpu_count(logical=False), + cpu=registry(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', ['ProcessorNameString', 'Identifier']), + windows=registry(r'SOFTWARE\Microsoft\Windows NT\CurrentVersion', ['CurrentBuildNumber', 'UBR', 'DisplayVersion'])) + + +def preflight(output, declaration): + """One bounded idle observation; no waiting until green or modifying the host.""" + write_json(output / 'environment-policy.json', POLICY) + write_json(output / 'environment-declaration.json', declaration) + if (declaration.get('no_known_concurrent_build_or_test') is not True or + not isinstance(declaration.get('known_concurrent_work'), list) or + declaration.get('host_kind') not in ('shared_workstation', 'hosted_vm', 'dedicated_host')): + raise ValueError('explicit host/concurrent-work declaration required') + value = dict(status='observing', host=host_identity(), started_utc=utc(), intervals=[], + processes_before=process_snapshot(), declaration=declaration, policy=POLICY) + write_json(output / 'environment.json', value) + try: + power = output / 'power-policy' + power.mkdir() + value['power_policy'] = capture(['powercfg', '/getactivescheme'], power, timeout=10) + if value['power_policy']['exit_code'] != 0: + raise ValueError('power policy observation failed') + before = cpu_snapshot() + for _ in range(POLICY['idle_intervals']): + time.sleep(POLICY['idle_interval_seconds']) + after = cpu_snapshot() + value['intervals'].append(dict(cpu_delta(before, after), before=before, after=after)) + write_json(output / 'environment.json', value) + before = after + value['processes_after'] = process_snapshot() + value['status'] = 'low_activity_observed' if qualify_idle(value['intervals']) else 'rejected_busy_host' + except BaseException as error: + value.update(status='failed', error=f'{type(error).__name__}: {error}') + raise + finally: + value['finished_utc'] = utc() + write_json(output / 'environment.json', value) + return value diff --git a/experiments/measure/r54_hosted.py b/experiments/measure/r54_hosted.py new file mode 100644 index 00000000..e30214bd --- /dev/null +++ b/experiments/measure/r54_hosted.py @@ -0,0 +1,74 @@ +"""One owned GitHub-hosted Windows build followed by the original R54 batches.""" +import argparse +import json +import os +from pathlib import Path + +from experiments.measure.r54_batch import run, sha, utc, write_json +from experiments.measure.r54_build import prepare_builds +from experiments.measure.r54_contract import ROOT, SEEDS +from experiments.measure.r54_inputs import git, require + +CPU_BOUNDARY_BRANCHES = ('perf/grapheme-cpu-hosted-r54', 'perf/grapheme-cpu-hosted-r54-setup2', + 'perf/grapheme-cpu-hosted-r54-entry3') + + +def hosted_identity(branch='perf/grapheme-hosted-r54'): + names = ('GITHUB_ACTIONS', 'RUNNER_ENVIRONMENT', 'RUNNER_OS', 'GITHUB_REPOSITORY', + 'GITHUB_SHA', 'GITHUB_REF', 'GITHUB_RUN_ID', 'GITHUB_RUN_NUMBER', 'GITHUB_RUN_ATTEMPT', + 'GITHUB_EVENT_NAME', 'ImageOS', 'ImageVersion') + identity = {name: os.environ.get(name) for name in names} + require(identity['GITHUB_ACTIONS'] == 'true' and identity['RUNNER_ENVIRONMENT'] == 'github-hosted' and + identity['RUNNER_OS'] == 'Windows' and os.name == 'nt', 'requires an actual GitHub-hosted Windows job') + require(identity['GITHUB_REPOSITORY'] == 'JavaZeroo/termforge' and identity['GITHUB_EVENT_NAME'] == 'push', + 'unexpected repository or event') + branches = (branch,) if isinstance(branch, str) else branch + require(identity['GITHUB_REF'] in ('refs/heads/' + name for name in branches), 'unexpected experiment branch') + require(identity['GITHUB_RUN_ATTEMPT'] == '1', 'a workflow rerun must not replace the first attempt') + require(identity['GITHUB_SHA'] == git(ROOT, 'rev-parse', 'HEAD'), 'workflow/source identity mismatch') + return identity + + +def execute(output): + output = Path(output).resolve() + require(output.is_relative_to(ROOT / 'local'), 'hosted artifacts must stay under the workspace local directory') + output.mkdir(parents=True, exist_ok=False) + manifest = dict(schema='termforge-r54-hosted-v1', status='preparing', started_utc=utc(), + source=git(ROOT, 'rev-parse', 'HEAD'), runner_sha256=sha(Path(__file__)), + performance_gate='not_evaluated', batches=[]) + try: + manifest['hosted_identity'] = hosted_identity() + write_json(output / 'hosted.json', manifest) + prepare_builds(output / 'bundle') + declaration = dict(host_kind='hosted_vm', no_known_concurrent_build_or_test=True, + known_concurrent_work=['This job completed setup, tests and both builds before measurement.', + 'GitHub runner service and operating-system background services remain.', + 'Physical host scheduling and neighboring virtual machines are unobserved.']) + write_json(output / 'environment-declaration.json', declaration) + for seed in SEEDS: + manifest['status'] = 'running' + manifest['active_seed'] = seed + write_json(output / 'hosted.json', manifest) + result = run(output / 'bundle/plan-v1.json', output / 'bundle', output / 'environment-declaration.json', + output / f'batch-{seed}', seed) + manifest['batches'].append(result) + require(result['status'] == 'complete_diagnostic', f'batch {seed} stopped; remaining seeds not launched') + manifest.update(status='complete_diagnostic', active_seed=None) + except BaseException as error: + manifest.update(status='failed', error=f'{type(error).__name__}: {error}') + raise + finally: + manifest['finished_utc'] = utc() + write_json(output / 'hosted.json', manifest) + return manifest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--out', type=Path, required=True) + args = parser.parse_args() + print(json.dumps(execute(args.out))) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/r54_inputs.py b/experiments/measure/r54_inputs.py new file mode 100644 index 00000000..2ce6d487 --- /dev/null +++ b/experiments/measure/r54_inputs.py @@ -0,0 +1,131 @@ +"""Verify the frozen plan against a retained Windows common-harness build bundle.""" +import json +from pathlib import Path +import subprocess + +from experiments.measure.r54_batch import sha +from experiments.measure.r54_contract import ROOT, SOURCES, canonical, protocol +import hashlib + +OVERLAYS = ('experiments/candidates/alacritty/Cargo.toml', + 'experiments/candidates/alacritty/src/measure.rs') +IDENTITY = dict(schema='termforge-core-cost-build-v1', allocator_wrapper_compiled=False, + timing_gates_compiled=True, projection_selectable=True) + + +def read(path): + return json.loads(Path(path).read_text(encoding='utf-8')) + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def git(directory, *args): + return subprocess.check_output(['git', *args], cwd=directory, timeout=30).decode('utf-8').strip() + + +def load_plan(path): + value = read(path) + require(canonical(value['protocol']) == canonical(protocol()), 'frozen protocol changed') + require(value['protocol_sha256'] == hashlib.sha256(canonical(protocol())).hexdigest(), 'protocol hash mismatch') + require(value['phase'] == 'prepared_not_built_or_measured', 'plan phase changed') + require(value['performance_gate'] == 'not_evaluated', 'plan cannot pass performance') + require(value['builds'] == {name: None for name in SOURCES} and value['environment_evidence'] is None, + 'frozen preparation plan was rewritten with results') + for name, expected in value['harness_sha256'].items(): + require(sha(ROOT / name) == expected, f'harness changed: {name}') + require(set(value['harness_sha256']) == {*OVERLAYS, 'experiments/measure/run.py', + 'experiments/measure/r54_contract.py'}, 'incomplete harness identity') + return value + + +def load_builds(plan_path, bundle): + """Audit artifacts and current sources; logs remain evidence, not a signature. + + This consumes the retained v1 preparation layout. No source, build receipt or + binary is reconstructed, edited or retried by the measurement entry point. + """ + bundle = Path(bundle).resolve(strict=True) + plan = load_plan(plan_path) + build_path = bundle / 'build-plan.json' + build, audit = read(build_path), read(bundle / 'build-audit.json') + require(build['schema'] == 'termforge-r54-local-build-plan-v1', 'unknown build bundle') + require(build['frozen_plan']['sha256'] == sha(plan_path), 'build belongs to another plan') + require(build['protocol_sha256'] == plan['protocol_sha256'], 'build protocol mismatch') + require(set(build['builds']) == set(SOURCES) and audit['passed'] is True, 'incomplete build audit') + overrides = ('RUSTFLAGS', 'CARGO_ENCODED_RUSTFLAGS', 'CARGO_BUILD_TARGET', 'CARGO_BUILD_RUSTFLAGS', + 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS', 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER', + 'RUSTC', 'RUSTC_WRAPPER', 'RUSTC_WORKSPACE_WRAPPER') + require(all(build['environment'][name] in (None, '') for name in overrides), 'unreviewed compiler override') + require(build['additional_build_environment'] == {'RUSTC_LOG': 'rustc_codegen_ssa::back::link=info'}, + 'unreviewed build environment') + for name in plan['harness_sha256']: + original = subprocess.check_output(['git', 'show', f"{build['harness_source']}:{name}"], cwd=ROOT, timeout=30) + require(original.replace(b'\r\n', b'\n') == (ROOT / name).read_bytes().replace(b'\r\n', b'\n'), + f'harness source mismatch: {name}') + commands, identities, evidence = {}, {}, {} + evidence['build-plan.json'] = sha(build_path) + evidence['build-audit.json'] = sha(bundle / 'build-audit.json') + targets = [] + for variant, source in SOURCES.items(): + spec = build['builds'][variant] + checkout = bundle / ('source-' + variant) + require(spec['commit'] == source['commit'] == git(checkout, 'rev-parse', 'HEAD'), 'source commit mismatch') + require(spec['tree'] == git(checkout, 'rev-parse', 'HEAD^{tree}'), 'source tree mismatch') + require(spec['candidate_tree'] == git(checkout, 'rev-parse', 'HEAD:experiments/candidates/alacritty'), 'candidate tree mismatch') + require(spec['version'] == source['version'], 'core version mismatch') + require(set(spec['overlay_files']) == set(OVERLAYS), 'unexpected harness overlay') + changed = git(checkout, 'diff', '--name-only', 'HEAD').splitlines() + require(set(changed) == set(spec['changed_paths']) == set(OVERLAYS), 'source changes outside harness') + files = set(git(checkout, 'ls-files', 'experiments/candidates/alacritty').splitlines()) + require(files == set(spec['before_files']) == set(spec['after_files']), 'incomplete source manifest') + for name in files: + require(sha(checkout / name) == spec['after_files'][name], f'changed build input: {name}') + if name in OVERLAYS: + require(spec['after_files'][name] == plan['harness_sha256'][name], 'different common harness') + else: + require(spec['before_files'][name] == spec['after_files'][name], 'changed original source') + target = Path(spec['target_dir']).resolve(strict=True) + targets.append(target) + expected_command = ['cargo', '+1.92.0', 'build', '--manifest-path', + str(checkout / 'experiments/candidates/alacritty/Cargo.toml'), + '--release', '--locked', '--offline', '--no-default-features', '--jobs', '1', + '--bin', 'alacritty-measure', '--target', 'x86_64-pc-windows-msvc', + '--target-dir', str(target), '-vv'] + require(spec['command'] == expected_command, 'build command is outside the fixed Windows configuration') + receipt_path = bundle / (variant + '-build.json') + receipt, observed = read(receipt_path), audit['builds'][variant] + require(observed['source'] == source['commit'] and observed['version'] == source['version'] and + observed['candidate_tree'] == spec['candidate_tree'], 'build audit source mismatch') + require(receipt['status'] == 'built_identity_only' and receipt['exit_code'] == 0, 'build failed') + require(receipt['variant'] == variant and receipt['command'] == spec['command'], 'build receipt command mismatch') + require(receipt['build_plan_sha256'] == sha(build_path), 'receipt belongs to another build plan') + require(observed['receipt_sha256'] == sha(receipt_path), 'build receipt changed') + require(receipt['identity'] == observed['identity'] == IDENTITY, 'unexpected compiled allocator mode') + binary = target / 'x86_64-pc-windows-msvc/release/alacritty-measure.exe' + binary_identity = dict(bytes=binary.stat().st_size, sha256=sha(binary)) + require(binary_identity == receipt['binary'] == observed['binary'], 'binary changed since build') + require(sha(Path(observed['linker']['path'])) == observed['linker']['sha256'], 'linker changed since audit') + require(set(receipt['logs']) == {variant + '-build.stdout', variant + '-build.stderr'}, 'missing build logs') + for name, identity in receipt['logs'].items(): + require(Path(name).name == name, 'log must stay in the bundle') + path = bundle / name + require(path.stat().st_size == identity['bytes'] and sha(path) == identity['sha256'], 'build log changed') + evidence[name] = sha(path) + patch = variant + '-harness.patch' + require(sha(bundle / patch) == spec['overlay_patch_sha256'], 'harness patch changed') + require(read(bundle / (variant + '-identity.stdout')) == IDENTITY, 'recorded binary identity mismatch') + evidence.update({receipt_path.name: sha(receipt_path), patch: sha(bundle / patch)}) + for suffix in ('-identity.stdout', '-identity.stderr'): + evidence[variant + suffix] = sha(bundle / (variant + suffix)) + commands[variant] = [str(binary)] + identities[variant] = dict(source=source, binary=binary_identity, + candidate_tree=spec['candidate_tree'], tracked_files=len(files)) + require(targets[0] != targets[1], 'shared target directory') + return dict(commands=commands, builds=identities, evidence=evidence, + bundle=str(bundle), plan_sha256=sha(plan_path), + protocol_sha256=plan['protocol_sha256'], harness_source=build['harness_source'], + rustc_vv=build['rustc_vv'], cargo_vv=build['cargo_vv'], + build_environment=build['environment'], additional_build_environment=build['additional_build_environment']) diff --git a/experiments/measure/r54_publish.py b/experiments/measure/r54_publish.py new file mode 100644 index 00000000..426fb9e0 --- /dev/null +++ b/experiments/measure/r54_publish.py @@ -0,0 +1,192 @@ +"""Publish a fixed synthetic-artifact whitelist from the owned hosted job.""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import re + +from experiments.measure.r54_batch import sha, write_json +from experiments.measure.r54_contract import ROOT, SEEDS +from experiments.measure.r54_hosted import CPU_BOUNDARY_BRANCHES, hosted_identity +from experiments.measure.r54_inputs import require + +EXPERIMENTS = { + 'cost': dict(branch='perf/grapheme-hosted-r54', raw_schema='termforge-r54-hosted-v1', + publication_schema='termforge-r54-hosted-publication-v1'), + 'cpu-boundary': dict(branch=CPU_BOUNDARY_BRANCHES, raw_schema='termforge-r54-cpu-hosted-v1', + publication_schema='termforge-r54-cpu-hosted-publication-v1'), +} + + +def public_environment(value): + """CPU counters remain; process names/PIDs and unrecognized fields do not.""" + fields = ('status', 'host', 'started_utc', 'finished_utc', 'intervals', 'declaration', 'policy', 'power_policy', 'error', 'scope') + result = {name: value[name] for name in fields if name in value} + if 'host' in result: + host = value['host'] + result['host'] = {name: host[name] for name in ('platform', 'python', 'psutil', 'cpu_logical', 'cpu_physical', 'cpu', 'windows') if name in host} + return result + + +def validate_raw_identity(root, identity, schema='termforge-r54-hosted-v1'): + value = json.loads((root / 'hosted.json').read_text(encoding='utf-8')) + require(value['schema'] == schema and value['hosted_identity'] == identity and + value['source'] == identity['GITHUB_SHA'], 'raw evidence belongs to another run, attempt or source') + + +def windows_short_path(path): + """Ask Windows for the actual alias; guessing RUNNER~1 is not reliable.""" + import ctypes + from ctypes import wintypes + function = ctypes.WinDLL('kernel32', use_last_error=True).GetShortPathNameW + function.argtypes = [wintypes.LPCWSTR, wintypes.LPWSTR, wintypes.DWORD] + function.restype = wintypes.DWORD + length = function(path, None, 0) + if not length: + raise ctypes.WinError(ctypes.get_last_error()) + buffer = ctypes.create_unicode_buffer(length) + copied = function(path, buffer, length) + if not copied or copied >= length: + raise ValueError('short-path lookup failed or changed during publication') + return buffer.value + + +def replacement_forms(prefixes): + forms = [] + for prefix, alias in prefixes: + forms.extend((form, alias) for form in (prefix.replace('\\', '\\\\'), prefix, prefix.replace('\\', '/'))) + return sorted(set(forms), key=lambda item: len(item[0]), reverse=True) + + +def redact_paths(data, replacements): + # Preserve arbitrary log bytes. The hosted Windows path prefixes are ASCII + # and may use a different case or a compiler-escaped 8.3 alias. + for before, after in replacements: + data = re.sub(re.escape(before.encode('utf-8')), lambda _: after.encode('utf-8'), data, flags=re.IGNORECASE) + return data + + +def publish_jsonl(original, replacements): + # JSONL is appended, unlike the atomic JSON manifests. A stop can leave an + # incomplete final row. Preserve it and the complete rows, mark the parse + # problem, and continue exporting independently retained sample streams. + status = dict(complete=True, error=None) + for index, line in enumerate(original.splitlines(), start=1): + try: + json.loads(line.decode('utf-8')) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + status.update(complete=False, error=f'{type(error).__name__}: {error}', error_line=index) + break + return redact_paths(original, replacements), status + + +def selected_paths(root, experiment='cost'): + """No recursive directory export: only named builder and runner artifacts.""" + require(experiment in EXPERIMENTS, 'unknown experiment') + names = ['hosted.json', 'environment-declaration.json'] + names += ['bundle/' + name for name in ('preparation.json', 'plan-v1.json', 'build-plan.json', 'build-audit.json')] + for variant in ('baseline', 'candidate'): + names += [f'bundle/{variant}-harness.patch', f'bundle/{variant}-build.json'] + for step in ('checkout', 'fetch', 'build', 'identity'): + names += [f'bundle/{variant}-{step}.{suffix}' for suffix in ('stdout', 'stderr', 'process.json')] + for route in ('project', 'interactive'): + names += [f'bundle/sanity-{variant}-{route}/{stream}.bin' for stream in ('stdout', 'stderr')] + if experiment == 'cost': + for seed in SEEDS: + prefix = f'batch-{seed}/' + names += [prefix + name for name in ('manifest.json', 'runner.json', 'environment.json', 'environment-policy.json', + 'environment-declaration.json', 'samples/context.json', 'samples/manifest.json', 'samples/samples.jsonl', 'samples/summary.json')] + for directory in ('baseline-identity', 'candidate-identity', 'power-policy', 'power-policy-after'): + names += [prefix + directory + '/' + name for name in ('record.json', 'stdout.bin', 'stderr.bin')] + for sequence in range(112): + names += [prefix + f'samples/{sequence:03d}/' + name for name in ('record.json', 'stdout.bin', 'stderr.bin')] + else: + prefix = 'diagnostic/' + names += [prefix + name for name in ('manifest.json', 'runner.json', 'environment.json', 'binding.json', + 'plan-v1.json', 'observations/context.json', 'observations/protocol.json', 'observations/manifest.json')] + for directory in ('baseline-identity', 'candidate-identity'): + names += [prefix + directory + '/' + name for name in ('record.json', 'stdout.bin', 'stderr.bin')] + for sequence in range(6): + names += [prefix + f'observations/{sequence:03d}/' + name for name in ('record.json', 'stdout.bin', 'stderr.bin')] + return [Path(name) for name in names if (root / name).is_file()] + + +def publish(root, output, experiment='cost'): + require(experiment in EXPERIMENTS, 'unknown experiment') + spec = EXPERIMENTS[experiment] + identity = hosted_identity(spec['branch']) + root, output = Path(root).resolve(), Path(output).resolve() + require(root.is_relative_to(ROOT / 'local') and output.is_relative_to(ROOT / 'local'), 'publication paths must stay in workspace local') + require(root != output and not output.is_relative_to(root), 'public output must be separate from raw records') + require(not output.exists(), 'public output already exists') + validate_raw_identity(root, identity, spec['raw_schema']) + staging = output.with_name(output.name + '.partial') + require(staging.is_relative_to(ROOT / 'local'), 'staging path escaped workspace') + staging.mkdir(parents=True, exist_ok=False) + prefixes = [(str(ROOT), '${WORKSPACE}'), (os.environ.get('USERPROFILE', ''), '${PROFILE}'), + (os.environ.get('RUNNER_TEMP', ''), '${RUNNER_TEMP}')] + prefixes = [(prefix, alias) for prefix, alias in prefixes if prefix] + prefixes += [(windows_short_path(prefix), alias) for prefix, alias in prefixes] + replacements = replacement_forms(prefixes) + + def text(value): + return redact_paths(value.encode('utf-8'), replacements).decode('utf-8') + + def transform(value): + if isinstance(value, dict): + return {key: transform(item) for key, item in value.items() if key != 'child_pid'} + if isinstance(value, list): + return [transform(item) for item in value] + return text(value) if isinstance(value, str) else value + + inventory = [] + for relative in selected_paths(root, experiment): + source = root / relative + require(source.resolve().is_relative_to(root), 'artifact path escaped raw directory') + original = source.read_bytes() + jsonl_status = None + # The measurement EXE consumes only public, fixed synthetic workloads. + # Preserve its raw streams even on failure. Other logs can contain host + # path prefixes; redact those without discarding their full contents. + raw_sample = bool(re.fullmatch(r'(?:batch-\d+/samples/\d{3}|bundle/sanity-(?:baseline|candidate)-(?:project|interactive)|diagnostic/(?:observations/\d{3}|(?:baseline|candidate)-identity))/(?:stdout|stderr)\.bin', relative.as_posix())) + if raw_sample: + data, treatment = original, 'byte_exact_owned_synthetic_stream' + elif relative.suffix == '.json': + value = json.loads(original.decode('utf-8')) + if relative.name == 'environment.json': + value = public_environment(value) + data = (json.dumps(transform(value), ensure_ascii=False, indent=2, allow_nan=False) + '\n').encode('utf-8') + treatment = 'json_whitelist_and_path_redaction' + elif relative.suffix == '.jsonl': + data, jsonl_status = publish_jsonl(original, replacements) + treatment = 'jsonl_path_redaction' + else: + data = redact_paths(original, replacements) + treatment = 'complete_log_with_path_prefix_redaction' + target = staging / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + inventory.append(dict(path=relative.as_posix(), original_bytes=len(original), + original_sha256=hashlib.sha256(original).hexdigest(), + published_bytes=len(data), published_sha256=sha(target), treatment=treatment, + jsonl_status=jsonl_status)) + write_json(staging / 'publication.json', dict(schema=spec['publication_schema'], + hosted_identity=identity, artifacts=inventory, raw_process_identities_published=False, + full_environment_variables_published=False, performance_gate='not_evaluated', + limitation='guest CPU evidence cannot establish exclusive physical host or stable frequency')) + staging.rename(output) + return dict(files=len(inventory), output=str(output), performance_gate='not_evaluated') + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--raw', type=Path, required=True) + parser.add_argument('--out', type=Path, required=True) + parser.add_argument('--experiment', choices=EXPERIMENTS, default='cost') + args = parser.parse_args() + print(json.dumps(publish(args.raw, args.out, args.experiment))) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md b/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md new file mode 100644 index 00000000..338d62ab --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md @@ -0,0 +1,36 @@ +# CPU 计量边界:一次共享宿主诊断 + +按[预声明合同](../../R54-CPU-DIAGNOSTIC.md),在共享Windows11宿主完成一次固定六位置诊断,所有进程退出0、输出和完整来源前后核验通过。**这不是原112位置成本批次,也没有性能通过结论。** 原hosted第24位置失败保留,当前结果不能反推其具体原因。 + +实际执行源码为本地提交 `ff0d7caca138b91683f16ce8d748eea6fbb289b7`,树 `796005cdcd0e330ae86d676ec5386125459d733d`;开始时工作树干净。运行时间为2026-10-27 12:17:43–12:18:00 UTC。完整工具源哈希在归档`runner.json`,发布版本中的执行代码与它逐文件一致;后续仅调整测试的runtime跳过边界及增加本记录。 + +环境为Windows11 build26200/UBR9711/25H2、Intel i7-9700、8逻辑/8物理CPU、CPython3.12.10、psutil7.2.2。明确共享宿主,未调整个人设置,未执行空闲资格筛选。复用[原构建记录](../2026-10-27-r54-runner/validation.json)中已审计的 `.3/.4` EXE;两份来源、harness、build命令/日志/覆盖、80/84文件身份、linker与实际`--identity`在运行前后重新校验。没有重建,二进制与旧hosted的两份构建不同。 + +下表均为CPU秒;余额比例的分母是全机总CPU区间,所有位置固定interactive/chunk1/observe0。它不报告吞吐或A/B成本比。 + +| 位置 | 核心 | 内部区间 | 完整子进程 | 区间外差值 | runner | 未归属余额 | 余额占比 | +|---|---|---:|---:|---:|---:|---:|---:| +| 0 | .3 | 2.125000 | 2.203125 | 0.078125 | 0 | 9.796875 | 49.920% | +| 1 | .4 | 2.140625 | 2.234375 | 0.093750 | 0.015625 | 8.859375 | 44.575% | +| 2 | .4 | 2.203125 | 2.296875 | 0.093750 | 0 | 6.656250 | 34.578% | +| 3 | .3 | 2.156250 | 2.265625 | 0.109375 | 0.015625 | 9.343750 | 47.611% | +| 4 | .3 | 2.125000 | 2.203125 | 0.078125 | 0 | 7.359375 | 38.231% | +| 5 | .4 | 2.031250 | 2.109375 | 0.078125 | 0.015625 | 9.812500 | 50.974% | + +此宿主确有可见的自身区间外CPU和runnerCPU,单扣内层区间不能准确区分这些部分。但完整扣除后的余额仍很大;原始计数和非原子采样可能有粒度差异,不把余额归因到某个后台进程,也不声称空闲、无扰动或物理宿主独占。下一步应在另行声明的独立hosted诊断中采用相同分量记录;不重复本地批次取绿,不调整原5%护栏,不将本诊断混入原正式样本。 + +相关measurement本地回归36/36(18.660s),新增真实进程/原字节产物边界及固定算例覆盖自身区间外CPU、超时、首错停止、不可覆盖与负余额。首次合成夹具使用venv重定向启动器,暴露了直接子进程与实际解释器的边界;改用实际解释器并核对子PID后验证通过,没有把派生进程CPU算进直接子进程。测试随后限定Windows CPython3.12、1–64逻辑CPU的native诊断;其他支持的环境仍可运行项目普通测试,并跳过这三个专属用例,跳过不表示计量通过。 + +runtime/CPU适用条件修正后的同组回归仍为36/36(17.069s)。实际执行代码与原测量源码哈希保持一致,没有重跑六位置诊断。 + +首次发布 `c76bab0` 的默认[PR](https://github.com/JavaZeroo/termforge/actions/runs/40504397868)/[push](https://github.com/JavaZeroo/termforge/actions/runs/40504336483)均通过(Windows46/46、Linux29/29);候选[PR](https://github.com/JavaZeroo/termforge/actions/runs/40504397941)/[push](https://github.com/JavaZeroo/termforge/actions/runs/40504336888)的Linux measurement为41通过/4跳过,Windows因未安装`psutil`在导入新测试时失败。原失败保留;修正候选工作流安装仓库已有固定`benchmarks/requirements.txt`,不跳过缺少依赖的测试。干净Python环境复现同一缺包错误,安装后4个新测试通过(1.583s)。修订CI单独跟踪,不将原两条候选失败写成通过。 + +[归档](evidence.zip)为100053B,SHA256 `9bec0731e08d7f2ef99d140a9e56acbc01468814cc68fb01fbdcfe30eaa8e10c`。内含46份清单证据及`publication.json`,其中16份stdout/stderr按原字节保留(12份观察、4份身份)。路径改为别名,子PID移除;清单分别保留原始/公开大小和SHA,原元数据里的来源哈希不回填成脱敏后哈希。未上传EXE、进程清单、任意环境变量或私有文件;完整本地原件保留。 + +离线复核命令,不执行任何测量: + +```powershell +python experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py +``` + +完整受控批次、allocator/projection及组件归因、预算提案与用户确认仍未完成;#54、#34与R2保持开放。 diff --git a/experiments/measure/results/2026-10-27-r54-cpu-boundary/evidence.zip b/experiments/measure/results/2026-10-27-r54-cpu-boundary/evidence.zip new file mode 100644 index 00000000..0414e68a Binary files /dev/null and b/experiments/measure/results/2026-10-27-r54-cpu-boundary/evidence.zip differ diff --git a/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json b/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json new file mode 100644 index 00000000..20b89e3e --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json @@ -0,0 +1,453 @@ +{ + "schema": "termforge-r54-cpu-boundary-validation-v1", + "performance_gate": "not_evaluated", + "measured_source": { + "commit": "ff0d7caca138b91683f16ce8d748eea6fbb289b7", + "worktree_status": "", + "sources": { + "experiments/measure/r54_cpu_diagnostic.py": "3071ae52fbddc0e080b586a78837498bdc2105072471ac12c6a48d15f5eb032e", + "experiments/measure/r54_environment.py": "60375a50e83637d27210d5ac0ff958cb0191cb65782fe91e69a741655c1d51f2", + "experiments/measure/r54_batch.py": "b7e19a85797b6d31c22d32eb9878e1975bfe733fdb09aa289da70fe0680e5b68", + "experiments/measure/r54_contract.py": "08c744f3d78e23439fecd8cc01348fdc1673c5397e16738266774fb49fbc76fa", + "experiments/measure/r54_inputs.py": "6e5430681254df364c8abc6b662969ca00991027878708ecdd3ba6f4e96f4eed", + "experiments/measure/run.py": "5643194c95c710790bee3a10e825cd338626222e88a9e2af194ad905b0718852" + } + }, + "measured_tree": "796005cdcd0e330ae86d676ec5386125459d733d", + "baseline": "ed0c12597d3b80a8806b2c6c24032cffaacf0945", + "manifest": { + "schema": "termforge-r54-cpu-diagnostic-v1", + "status": "accounting_complete", + "started_utc": "2026-10-27T12:17:43.046171+00:00", + "performance_gate": "not_evaluated", + "observations": { + "schema": "termforge-r54-cpu-observations-v1", + "status": "accounting_complete", + "started_utc": "2026-10-27T12:17:44.816067+00:00", + "attempted": 6, + "accounted_observations": 6, + "context_sha256": "ad54a604709f4a2f28d2af4349de08ebccf1f30d0f2d2238e5381c3005fd2707", + "protocol_sha256": "c92098b46db730cfd7939163831682e5650ca7c4995da40d13980cf7b355410e", + "performance_gate": "not_evaluated", + "active_sequence": null, + "finished_utc": "2026-10-27T12:17:59.527911+00:00" + }, + "finished_utc": "2026-10-27T12:18:00.346147+00:00" + }, + "environment": { + "host": { + "platform": "Windows-11-10.0.26200-SP0", + "python": "3.12.10", + "psutil": "7.2.2", + "cpu_logical": 8, + "cpu_physical": 8, + "cpu": { + "ProcessorNameString": "Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz", + "Identifier": "Intel64 Family 6 Model 158 Stepping 13" + }, + "windows": { + "CurrentBuildNumber": "26200", + "UBR": 9711, + "DisplayVersion": "25H2" + } + }, + "declaration": { + "host_kind": "shared_workstation", + "known_concurrent_work": [ + "This agent completed the measurement contract tests before this diagnostic and starts no concurrent owned build or test.", + "The user workstation, operating-system services and other applications are not isolated or changed.", + "Guest-visible system CPU is recorded; physical-host contention and clock stability remain unobserved." + ], + "purpose": "One predeclared six-observation CPU-boundary diagnostic, not an idle-qualified cost batch." + }, + "scope": "accounting diagnosis on declared host; no idle qualification or cost acceptance" + }, + "builds": { + "baseline": { + "source": { + "commit": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "version": "0.26.1-dev+termforge.3", + "states": { + "project": "11966868740884721388", + "interactive": "8035857386658397897" + } + }, + "binary": { + "bytes": 523264, + "sha256": "33cb90beebffc57f07aa993b8ea635eaf461b49d86c143939a41f6242c087b6d" + }, + "candidate_tree": "38f901ce2407d73ab7d4ae70987c294849bafa92", + "tracked_files": 80 + }, + "candidate": { + "source": { + "commit": "3d6d44d3b3e9a9a3e41da98e8711e9f19a211fd8", + "version": "0.26.1-dev+termforge.4", + "states": { + "project": "16178973312633948535", + "interactive": "6154007251599136798" + } + }, + "binary": { + "bytes": 579584, + "sha256": "d4d068be44a0a9ec733ad17f0323765ee38ffbf775e6efacb4632c1dce6b3f01" + }, + "candidate_tree": "2cce55fcf2d379440110bbbf8612117cce3edf64", + "tracked_files": 84 + } + }, + "observations": [ + { + "item": { + "sequence": 0, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.125, + "child_cpu": { + "creation_filetime": 134375770648262487, + "exit_filetime": 134375770672853118, + "kernel_seconds": 0.921875, + "user_seconds": 1.28125, + "total_seconds": 2.203125 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 19.625, + "busy_seconds": 12.0, + "busy_percent": 61.146496815286625, + "before": { + "user": 4467.78125, + "kernel_including_idle": 24590.65625, + "idle": 19905.25, + "system": 4685.40625, + "perf_counter_seconds": 3632.723437, + "utc": "2026-10-27T12:17:44.825276+00:00" + }, + "after": { + "user": 4474.515625, + "kernel_including_idle": 24603.546875, + "idle": 19912.875, + "system": 4690.671875, + "perf_counter_seconds": 3635.1845603, + "utc": "2026-10-27T12:17:47.286686+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 9.875, + "inner_only_balance_percent": 50.318471337579616, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 9.796875, + "unassigned_balance_percent": 49.9203821656051 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 1, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.140625, + "child_cpu": { + "creation_filetime": 134375770673032344, + "exit_filetime": 134375770697758213, + "kernel_seconds": 0.625, + "user_seconds": 1.609375, + "total_seconds": 2.234375 + }, + "runner_cpu_seconds": 0.015625, + "environment": { + "total_seconds": 19.875, + "busy_seconds": 11.109375, + "busy_percent": 55.89622641509434, + "before": { + "user": 4474.515625, + "kernel_including_idle": 24603.671875, + "idle": 19912.96875, + "system": 4690.703125, + "perf_counter_seconds": 3635.2004591, + "utc": "2026-10-27T12:17:47.302400+00:00" + }, + "after": { + "user": 4480.765625, + "kernel_including_idle": 24617.296875, + "idle": 19921.734375, + "system": 4695.5625, + "perf_counter_seconds": 3637.6748693, + "utc": "2026-10-27T12:17:49.776942+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 8.96875, + "inner_only_balance_percent": 45.125786163522015, + "child_outside_inner_seconds": 0.09375, + "runner_cpu_seconds": 0.015625, + "unassigned_balance_seconds": 8.859375, + "unassigned_balance_percent": 44.575471698113205 + }, + "state_fnv1a64": "6154007251599136798" + }, + { + "item": { + "sequence": 2, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.203125, + "child_cpu": { + "creation_filetime": 134375770697951230, + "exit_filetime": 134375770722075739, + "kernel_seconds": 0.828125, + "user_seconds": 1.46875, + "total_seconds": 2.296875 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 19.25, + "busy_seconds": 8.953125, + "busy_percent": 46.50974025974026, + "before": { + "user": 4480.796875, + "kernel_including_idle": 24617.390625, + "idle": 19921.8125, + "system": 4695.578125, + "perf_counter_seconds": 3637.6917606, + "utc": "2026-10-27T12:17:49.793919+00:00" + }, + "after": { + "user": 4485.46875, + "kernel_including_idle": 24631.96875, + "idle": 19932.109375, + "system": 4699.859375, + "perf_counter_seconds": 3640.1066225, + "utc": "2026-10-27T12:17:52.208557+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 6.75, + "inner_only_balance_percent": 35.064935064935064, + "child_outside_inner_seconds": 0.09375, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 6.65625, + "unassigned_balance_percent": 34.577922077922075 + }, + "state_fnv1a64": "6154007251599136798" + }, + { + "item": { + "sequence": 3, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.15625, + "child_cpu": { + "creation_filetime": 134375770722276046, + "exit_filetime": 134375770746664698, + "kernel_seconds": 0.75, + "user_seconds": 1.515625, + "total_seconds": 2.265625 + }, + "runner_cpu_seconds": 0.015625, + "environment": { + "total_seconds": 19.625, + "busy_seconds": 11.625, + "busy_percent": 59.23566878980892, + "before": { + "user": 4485.5, + "kernel_including_idle": 24632.0625, + "idle": 19932.171875, + "system": 4699.890625, + "perf_counter_seconds": 3640.1245048, + "utc": "2026-10-27T12:17:52.226226+00:00" + }, + "after": { + "user": 4491.75, + "kernel_including_idle": 24645.4375, + "idle": 19940.171875, + "system": 4705.265625, + "perf_counter_seconds": 3642.5663847, + "utc": "2026-10-27T12:17:54.668146+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 9.46875, + "inner_only_balance_percent": 48.2484076433121, + "child_outside_inner_seconds": 0.109375, + "runner_cpu_seconds": 0.015625, + "unassigned_balance_seconds": 9.34375, + "unassigned_balance_percent": 47.611464968152866 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 4, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.125, + "child_cpu": { + "creation_filetime": 134375770746872014, + "exit_filetime": 134375770770988460, + "kernel_seconds": 0.578125, + "user_seconds": 1.625, + "total_seconds": 2.203125 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 19.25, + "busy_seconds": 9.5625, + "busy_percent": 49.675324675324674, + "before": { + "user": 4491.78125, + "kernel_including_idle": 24645.53125, + "idle": 19940.25, + "system": 4705.28125, + "perf_counter_seconds": 3642.5841586, + "utc": "2026-10-27T12:17:54.685918+00:00" + }, + "after": { + "user": 4497.65625, + "kernel_including_idle": 24658.90625, + "idle": 19949.9375, + "system": 4708.96875, + "perf_counter_seconds": 3644.9979968, + "utc": "2026-10-27T12:17:57.098922+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 7.4375, + "inner_only_balance_percent": 38.63636363636363, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 7.359375, + "unassigned_balance_percent": 38.23051948051948 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 5, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "exit_code": 0, + "timed_out": false, + "inner_cpu_seconds": 2.03125, + "child_cpu": { + "creation_filetime": 134375770771152026, + "exit_filetime": 134375770795208267, + "kernel_seconds": 0.953125, + "user_seconds": 1.15625, + "total_seconds": 2.109375 + }, + "runner_cpu_seconds": 0.015625, + "environment": { + "total_seconds": 19.25, + "busy_seconds": 11.9375, + "busy_percent": 62.01298701298701, + "before": { + "user": 4497.65625, + "kernel_including_idle": 24659.03125, + "idle": 19950.03125, + "system": 4709.0, + "perf_counter_seconds": 3645.0124583, + "utc": "2026-10-27T12:17:57.114619+00:00" + }, + "after": { + "user": 4503.953125, + "kernel_including_idle": 24671.984375, + "idle": 19957.34375, + "system": 4714.640625, + "perf_counter_seconds": 3647.4196989, + "utc": "2026-10-27T12:17:59.521405+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 9.90625, + "inner_only_balance_percent": 51.46103896103896, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.015625, + "unassigned_balance_seconds": 9.8125, + "unassigned_balance_percent": 50.97402597402598 + }, + "state_fnv1a64": "6154007251599136798" + } + ], + "formal_cost_samples": 0, + "old_hosted_event_cause": "unknown", + "archive": { + "bytes": 100053, + "sha256": "9bec0731e08d7f2ef99d140a9e56acbc01468814cc68fb01fbdcfe30eaa8e10c", + "inventory_files": 46, + "byte_exact_streams": 16, + "path_prefixes_aliased": true, + "child_pid_omitted": true + }, + "local_tests": { + "before_measurement": { + "count": 36, + "seconds": 18.66 + }, + "after_test_runtime_boundary_fix": { + "count": 36, + "seconds": 17.069, + "log_sha256": "e3ab51e002dd2446be8ee34943895825e433197f5d7bff6338b4cf5c3763edf7" + } + }, + "limits": [ + "Shared host, no idle qualification and no performance ratios.", + "Residual includes possible other guest activity, counter quantization and non-atomic endpoints.", + "New instrumentation does not recreate missing whole-child counters for the old hosted failure.", + "Full controlled batches, wrapper/projection and component attribution, budget proposal and user confirmation remain incomplete." + ], + "initial_ci": { + "source": "c76bab0281843fca298af8b0d9ba521247ac745a", + "all_attempts": 1, + "default_pr": { + "run": 40504397868, + "status": "success", + "windows": 46, + "linux": 29 + }, + "default_push": { + "run": 40504336483, + "status": "success", + "windows": 46, + "linux": 29 + }, + "candidate_pr": { + "run": 40504397941, + "status": "failure", + "windows_failure": "ModuleNotFoundError: No module named psutil", + "linux_passed": 41, + "linux_skipped": 4 + }, + "candidate_push": { + "run": 40504336888, + "status": "failure", + "windows_failure": "ModuleNotFoundError: No module named psutil", + "linux_passed": 41, + "linux_skipped": 4 + }, + "fix": "Install the existing pinned benchmarks/requirements.txt in candidate CI; no measurement source or observation change." + } +} diff --git a/experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py b/experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py new file mode 100644 index 00000000..f60f9e5a --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py @@ -0,0 +1,73 @@ +"""Recheck retained synthetic evidence without extracting files or launching a cost run.""" +import hashlib +import json +from pathlib import Path +import sys +import zipfile + +ROOT = Path(__file__).resolve().parents[4] +sys.path.insert(0, str(ROOT)) +from experiments.measure.r54_contract import validate_sample + + +def verify(): + path = Path(__file__).with_name('evidence.zip') + assert hashlib.sha256(path.read_bytes()).hexdigest() == '9bec0731e08d7f2ef99d140a9e56acbc01468814cc68fb01fbdcfe30eaa8e10c' + with zipfile.ZipFile(path) as archive: + read = lambda name: json.loads(archive.read(name)) + inventory = read('publication.json')['files'] + assert len(archive.namelist()) == 47 + assert len({row['path'] for row in inventory}) == 46 + assert set(archive.namelist()) == {row['path'] for row in inventory} | {'publication.json'} + for row in inventory: + data = archive.read(row['path']) + assert len(data) == row['published_bytes'] + assert hashlib.sha256(data).hexdigest() == row['published_sha256'] + if row['treatment'] == 'byte_exact_owned_synthetic_stream': + assert row['published_sha256'] == row['original_sha256'] + assert read('manifest.json')['status'] == 'accounting_complete' + manifest = read('observations/manifest.json') + assert manifest['attempted'] == manifest['accounted_observations'] == 6 + assert manifest['performance_gate'] == 'not_evaluated' + builds = read('binding.json')['builds'] + prior = json.loads((path.parent.parent / '2026-10-27-r54-runner/validation.json').read_text(encoding='utf-8')) + for variant in ('baseline', 'candidate'): + assert builds[variant]['binary'] == prior['builds'][variant]['binary'] + assert builds[variant]['source']['commit'] == prior['builds'][variant]['source'] + for index, variant in enumerate(('baseline', 'candidate', 'candidate', 'baseline', 'baseline', 'candidate')): + prefix = f'observations/{index:03d}/' + row = read(prefix + 'record.json') + assert row['item'] == dict(sequence=index, variant=variant, route='interactive', chunk=1) + assert row['status'] == 'accounted' and row['exit_code'] == 0 and not row['timed_out'] + assert 'child_pid' not in row + for name in ('stdout.bin', 'stderr.bin'): + data = archive.read(prefix + name) + assert len(data) == row['streams'][name]['bytes'] + assert hashlib.sha256(data).hexdigest() == row['streams'][name]['sha256'] + raw = read(prefix + 'stdout.bin') + assert raw == row['result'] + validate_sample(raw, row['item']) + env, child = row['environment'], row['child_cpu'] + before, after = env['before'], env['after'] + busy = after['user'] + after['system'] - before['user'] - before['system'] + total = busy + after['idle'] - before['idle'] + assert abs(busy - env['busy_seconds']) < 1e-8 + assert abs(total - env['total_seconds']) < 1e-8 + assert child['total_seconds'] == child['kernel_seconds'] + child['user_seconds'] + assert child['exit_filetime'] > child['creation_filetime'] + runner = row['runner_cpu_after_seconds'] - row['runner_cpu_before_seconds'] + assert runner == row['runner_cpu_seconds'] + accounted = row['accounting'] + inner = raw['interval_cpu_seconds'] + assert child['total_seconds'] >= inner + assert accounted['child_outside_inner_seconds'] == child['total_seconds'] - inner + assert accounted['inner_only_balance_seconds'] == busy - inner + assert accounted['runner_cpu_seconds'] == runner + assert accounted['unassigned_balance_seconds'] == busy - child['total_seconds'] - runner + assert abs(accounted['unassigned_balance_percent'] - accounted['unassigned_balance_seconds'] * 100 / total) < 1e-8 + return dict(files=47, hashed_evidence=46, byte_exact_streams=16, observations=6, + oracles_passed=6, accepted_cost_samples=0, performance_gate='not_evaluated') + + +if __name__ == '__main__': + print(json.dumps(verify())) diff --git a/experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md b/experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md new file mode 100644 index 00000000..10a4e13a --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md @@ -0,0 +1,54 @@ +# R54:新 hosted VM 的 CPU 分量诊断 + +本次按[预声明合同](../../R54-CPU-HOSTED.md)完成一次六位置诊断,全部输出正确、退出0,完整子进程/runner/全机计量可复核。**它没有执行空闲资格筛选或正式成本批次,不构成性能验收。** + +## 来源与执行身份 + +- 源码 `4772f4f937c4f0f220be1630592f84bed3a591c8`,树 `86e2342d16acd101fe182793d20aef3f1f017633`;与审查树一致。 +- [run 40512310499](https://github.com/JavaZeroo/termforge/actions/runs/40512310499),job `122217071656`,attempt1,创建push分支 `perf/grapheme-cpu-hosted-r54-entry3`。提供者元数据、作业日志及归档身份相符。 +- 实际映像 `windows-2025-vs2026` / `20261021.278.1`;Windows Server 2025 build26100、UBR33650、24H2,AMD EPYC 7763,guest4逻辑/2物理CPU,Python3.12.10、psutil7.2.2。 +- Rust1.92.0、MSVC14.51.36231 x64;Release、locked/offline、no-default-features、jobs1,独立target。构建前31项准备测试通过(14.042s),两次构建和四次短正确性通过后才进入诊断。 + +| 核心 | 固定来源 | 本次新EXE SHA-256 | 字节数 | +|---|---|---|---:| +| `.3` | `65988f5f20f62294ff52fcac5b5b81e563f93f3b` | `5f31a54e5543eca286e98a10b31aae060b0a52f2ccbcfb4bfc1d7983d6428c03` | 523264 | +| `.4` | `3d6d44d3b3e9a9a3e41da98e8711e9f19a211fd8` | `45a15862e2bd8c8ce93073923b68f23bc6f3f848aa147ed1ce6340135e214207` | 580096 | + +80/84个跟踪来源文件、原锁文件与两处共同harness覆盖均核对。Git blob和实际Windows检出换行不同的文件,逐个用完整SHA验证准确的LF→CRLF转换;没有修改证据来匹配仓库字节。4份共同harness/校验入口及164份原文件共168次来源核对,其中115份对应CRLF检出、53份直接匹配blob。它是独立构建,不沿用此前EXE身份。 + +## 实际六位置 + +固定interactive 22,000B、chunk1、observe0,内部CPU/墙钟至少2秒,顺序 `.3,.4,.4,.3,.3,.4`;单进程90秒、清理5秒。未重跑或补样,原完整oracle不变。 + +| sequence | 核心 | 自身区间外CPU/s | runner CPU/s | 全机未归属余额/s | 余额占全机总CPU区间 | +|---:|---|---:|---:|---:|---:| +| 0 | `.3` | 0.093750 | 0 | 0.500000 | 5.263158% | +| 1 | `.4` | 0.062500 | 0.015625 | 0.281250 | 3.333333% | +| 2 | `.4` | 0.078125 | 0 | 0.328125 | 3.888889% | +| 3 | `.3` | 0.078125 | 0 | 0.328125 | 3.860294% | +| 4 | `.3` | 0.062500 | 0 | 0.296875 | 3.492647% | +| 5 | `.4` | 0.078125 | 0 | 0.312500 | 3.703704% | + +余额为全机busy减完整子进程CPU及runner同区间CPU,允许正负;可能包括其他guest活动、计数粒度和非原子端点。sequence0即使扣除自身完整工作,余额仍超过原5%环境护栏的数值;本诊断不应用该护栏判通过。结果仅支持“补齐自身计量不能消除这个新环境的全部余额”,不将余额归因某一进程,不解释旧hosted第24位置失败,也不证明物理宿主排他或频率稳定。 + +没有计算吞吐比、没有改原v1两批各112位置及5%护栏,也没有接受产品性能容差。`accounting_complete`仅表示诊断记录完整;`performance_gate=not_evaluated`,正式成本验收样本为0。 + +## 保留的入口缺口 + +首次source `eed0ab7` 的 [40509157581](https://github.com/JavaZeroo/termforge/actions/runs/40509157581)在31项准备测试中的路径边界比较失败,实际构建/测量0。夹具根路径未规范化;本地8.3别名复现该缺陷,但旧job实际临时路径形式未记录。只规范化测试ROOT,生产路径边界未放宽。首次四条普通CI也在Windows的同一测试失败,Linux成功;元数据与错误摘录留在 [validation.json](validation.json)。 + +修订 `c14adb9a` 的四条普通CI及setup2默认CI均通过;在已发布source创建setup2时,仅出现无路径过滤的默认回归40511533857,没有CPU或候选workflow run。CPU入口随后去掉paths过滤,并单独预声明entry3;创建事件与attempt1限制保持。没有删除重建旧分支、重跑旧run或用新观察覆盖失败。触发解释及其未知范围见合同。 + +## 归档和复核 + +审核后的[evidence.zip](evidence.zip)与提供者产物字节相同:123388B,SHA-256 `263a3bf8e229b89f1e443b12e522daf69d30de403affa0dc49fe674c0dd8f241`,artifact `12738430576`。75文件中74份有原始/公开哈希清单,24份sanity/诊断identity/观察流保持原字节;元数据移除子PID、长短路径改别名,不含checkout、缓存、进程清单或WindTerm私有产物。 + +完整构建日志、来源清单、原始计数与输出均在归档;[validation.json](validation.json)整理实际身份和结论。运行以下只读复核,不启动编译或测量,需包含固定来源提交的Git历史: + +```powershell +python experiments/measure/results/2026-10-27-r54-cpu-hosted/verify.py +``` + +复核包含所有公开文件哈希、24份原字节流、来源及构建绑定、四次sanity、六次完整输出oracle和全部CPU等式。本地measurement40/40(19.436s),入口增量4/4(0.297s);实际hosted测试与上述本地测试身份分别保留。 + +后续仍需受控完整A/B、allocator/projection扰动与组件归因、可靠WindTerm基线及用户确认的逐项预算。#54、#34和R2保持未完成,产品核心与后端继续作为候选。 diff --git a/experiments/measure/results/2026-10-27-r54-cpu-hosted/evidence.zip b/experiments/measure/results/2026-10-27-r54-cpu-hosted/evidence.zip new file mode 100644 index 00000000..f1bdd091 Binary files /dev/null and b/experiments/measure/results/2026-10-27-r54-cpu-hosted/evidence.zip differ diff --git a/experiments/measure/results/2026-10-27-r54-cpu-hosted/validation.json b/experiments/measure/results/2026-10-27-r54-cpu-hosted/validation.json new file mode 100644 index 00000000..b3554a61 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-hosted/validation.json @@ -0,0 +1,2538 @@ +{ + "schema": "termforge-r54-cpu-hosted-validation-v1", + "source": "4772f4f937c4f0f220be1630592f84bed3a591c8", + "hosted_identity": { + "GITHUB_ACTIONS": "true", + "RUNNER_ENVIRONMENT": "github-hosted", + "RUNNER_OS": "Windows", + "GITHUB_REPOSITORY": "JavaZeroo/termforge", + "GITHUB_SHA": "4772f4f937c4f0f220be1630592f84bed3a591c8", + "GITHUB_REF": "refs/heads/perf/grapheme-cpu-hosted-r54-entry3", + "GITHUB_RUN_ID": "40512310499", + "GITHUB_RUN_NUMBER": "3", + "GITHUB_RUN_ATTEMPT": "1", + "GITHUB_EVENT_NAME": "push", + "ImageOS": "win25-vs2026", + "ImageVersion": "20261021.278.1" + }, + "host": { + "host": { + "platform": "Windows-2025Server-10.0.26100-SP0", + "python": "3.12.10", + "psutil": "7.2.2", + "cpu_logical": 4, + "cpu_physical": 2, + "cpu": { + "ProcessorNameString": "AMD EPYC 7763 64-Core Processor ", + "Identifier": "AMD64 Family 25 Model 1 Stepping 1" + }, + "windows": { + "CurrentBuildNumber": "26100", + "UBR": 33650, + "DisplayVersion": "24H2" + } + }, + "declaration": { + "host_kind": "hosted_vm", + "no_known_concurrent_build_or_test": true, + "known_concurrent_work": [ + "This job completed setup, tests and both audited builds before diagnosis.", + "GitHub runner service and operating-system background services remain.", + "Physical host scheduling and neighboring virtual machines are unobserved." + ], + "purpose": "One six-observation CPU-boundary diagnostic; no idle qualification or cost acceptance." + }, + "scope": "accounting diagnosis on declared host; no idle qualification or cost acceptance" + }, + "builds": { + "baseline": { + "source": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "version": "0.26.1-dev+termforge.3", + "candidate_tree": "38f901ce2407d73ab7d4ae70987c294849bafa92", + "tracked_candidate_files": 80, + "changed_paths": [ + "experiments/candidates/alacritty/Cargo.toml", + "experiments/candidates/alacritty/src/measure.rs" + ], + "binary": { + "bytes": 523264, + "sha256": "5f31a54e5543eca286e98a10b31aae060b0a52f2ccbcfb4bfc1d7983d6428c03" + }, + "identity": { + "allocator_wrapper_compiled": false, + "projection_selectable": true, + "schema": "termforge-core-cost-build-v1", + "timing_gates_compiled": true + }, + "linker": { + "path": "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\bin\\HostX64\\x64\\link.exe", + "sha256": "a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d" + }, + "receipt_sha256": "886d9a3f024d46a221dc4833a485e0c9c2c4b6e6193dd42190fe10fc6bc70a8a" + }, + "candidate": { + "source": "3d6d44d3b3e9a9a3e41da98e8711e9f19a211fd8", + "version": "0.26.1-dev+termforge.4", + "candidate_tree": "2cce55fcf2d379440110bbbf8612117cce3edf64", + "tracked_candidate_files": 84, + "changed_paths": [ + "experiments/candidates/alacritty/Cargo.toml", + "experiments/candidates/alacritty/src/measure.rs" + ], + "binary": { + "bytes": 580096, + "sha256": "45a15862e2bd8c8ce93073923b68f23bc6f3f848aa147ed1ce6340135e214207" + }, + "identity": { + "allocator_wrapper_compiled": false, + "projection_selectable": true, + "schema": "termforge-core-cost-build-v1", + "timing_gates_compiled": true + }, + "linker": { + "path": "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\bin\\HostX64\\x64\\link.exe", + "sha256": "a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d" + }, + "receipt_sha256": "5d8e407aa31a9d2fc3921a15fcc15e295e9fa8a748acf2f40e81744d1cdaa914" + } + }, + "observations": [ + { + "item": { + "sequence": 0, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.234375, + "child_cpu": { + "creation_filetime": 134375815087328400, + "exit_filetime": 134375815111035208, + "kernel_seconds": 0.609375, + "user_seconds": 1.71875, + "total_seconds": 2.328125 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 9.5, + "busy_seconds": 2.828125, + "busy_percent": 29.769736842105264, + "before": { + "user": 224.96875, + "kernel_including_idle": 1054.8125, + "idle": 938.25, + "system": 116.5625, + "perf_counter_seconds": 320.6986669, + "utc": "2026-10-27T13:31:48.731677+00:00" + }, + "after": { + "user": 226.921875, + "kernel_including_idle": 1062.359375, + "idle": 944.921875, + "system": 117.4375, + "perf_counter_seconds": 323.0708238, + "utc": "2026-10-27T13:31:51.102693+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.59375, + "inner_only_balance_percent": 6.25, + "child_outside_inner_seconds": 0.09375, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 0.5, + "unassigned_balance_percent": 5.2631578947368425 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 1, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.015625, + "child_cpu": { + "creation_filetime": 134375815111116270, + "exit_filetime": 134375815132177544, + "kernel_seconds": 0.578125, + "user_seconds": 1.5, + "total_seconds": 2.078125 + }, + "runner_cpu_seconds": 0.015625, + "environment": { + "total_seconds": 8.4375, + "busy_seconds": 2.375, + "busy_percent": 28.14814814814815, + "before": { + "user": 226.921875, + "kernel_including_idle": 1062.359375, + "idle": 944.921875, + "system": 117.4375, + "perf_counter_seconds": 323.0773715, + "utc": "2026-10-27T13:31:51.111019+00:00" + }, + "after": { + "user": 228.546875, + "kernel_including_idle": 1069.171875, + "idle": 950.984375, + "system": 118.1875, + "perf_counter_seconds": 325.1850088, + "utc": "2026-10-27T13:31:53.217016+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.359375, + "inner_only_balance_percent": 4.2592592592592595, + "child_outside_inner_seconds": 0.0625, + "runner_cpu_seconds": 0.015625, + "unassigned_balance_seconds": 0.28125, + "unassigned_balance_percent": 3.3333333333333335 + }, + "state_fnv1a64": "6154007251599136798" + }, + { + "item": { + "sequence": 2, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.03125, + "child_cpu": { + "creation_filetime": 134375815132252696, + "exit_filetime": 134375815153338773, + "kernel_seconds": 0.5625, + "user_seconds": 1.546875, + "total_seconds": 2.109375 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 8.4375, + "busy_seconds": 2.4375, + "busy_percent": 28.88888888888889, + "before": { + "user": 228.546875, + "kernel_including_idle": 1069.234375, + "idle": 951.046875, + "system": 118.1875, + "perf_counter_seconds": 325.1909982, + "utc": "2026-10-27T13:31:53.223062+00:00" + }, + "after": { + "user": 230.296875, + "kernel_including_idle": 1075.921875, + "idle": 957.046875, + "system": 118.875, + "perf_counter_seconds": 327.3011683, + "utc": "2026-10-27T13:31:55.334541+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.40625, + "inner_only_balance_percent": 4.814814814814815, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 0.328125, + "unassigned_balance_percent": 3.888888888888889 + }, + "state_fnv1a64": "6154007251599136798" + }, + { + "item": { + "sequence": 3, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.03125, + "child_cpu": { + "creation_filetime": 134375815153414663, + "exit_filetime": 134375815174567926, + "kernel_seconds": 0.484375, + "user_seconds": 1.625, + "total_seconds": 2.109375 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 8.5, + "busy_seconds": 2.4375, + "busy_percent": 28.676470588235293, + "before": { + "user": 230.296875, + "kernel_including_idle": 1075.921875, + "idle": 957.046875, + "system": 118.875, + "perf_counter_seconds": 327.3071463, + "utc": "2026-10-27T13:31:55.340090+00:00" + }, + "after": { + "user": 232.109375, + "kernel_including_idle": 1082.609375, + "idle": 963.109375, + "system": 119.5, + "perf_counter_seconds": 329.4241257, + "utc": "2026-10-27T13:31:57.457105+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.40625, + "inner_only_balance_percent": 4.779411764705882, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 0.328125, + "unassigned_balance_percent": 3.860294117647059 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 4, + "variant": "baseline", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.046875, + "child_cpu": { + "creation_filetime": 134375815174640115, + "exit_filetime": 134375815195919780, + "kernel_seconds": 0.5625, + "user_seconds": 1.546875, + "total_seconds": 2.109375 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 8.5, + "busy_seconds": 2.40625, + "busy_percent": 28.308823529411764, + "before": { + "user": 232.109375, + "kernel_including_idle": 1082.609375, + "idle": 963.109375, + "system": 119.5, + "perf_counter_seconds": 329.4296819, + "utc": "2026-10-27T13:31:57.463164+00:00" + }, + "after": { + "user": 233.84375, + "kernel_including_idle": 1089.375, + "idle": 969.203125, + "system": 120.171875, + "perf_counter_seconds": 331.5593194, + "utc": "2026-10-27T13:31:59.593006+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.359375, + "inner_only_balance_percent": 4.227941176470588, + "child_outside_inner_seconds": 0.0625, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 0.296875, + "unassigned_balance_percent": 3.4926470588235294 + }, + "state_fnv1a64": "8035857386658397897" + }, + { + "item": { + "sequence": 5, + "variant": "candidate", + "route": "interactive", + "chunk": 1 + }, + "inner_cpu_seconds": 2.03125, + "child_cpu": { + "creation_filetime": 134375815195995616, + "exit_filetime": 134375815217130487, + "kernel_seconds": 0.453125, + "user_seconds": 1.65625, + "total_seconds": 2.109375 + }, + "runner_cpu_seconds": 0.0, + "environment": { + "total_seconds": 8.4375, + "busy_seconds": 2.421875, + "busy_percent": 28.703703703703702, + "before": { + "user": 233.84375, + "kernel_including_idle": 1089.4375, + "idle": 969.25, + "system": 120.1875, + "perf_counter_seconds": 331.5651614, + "utc": "2026-10-27T13:31:59.597040+00:00" + }, + "after": { + "user": 235.671875, + "kernel_including_idle": 1096.046875, + "idle": 975.265625, + "system": 120.78125, + "perf_counter_seconds": 333.6804548, + "utc": "2026-10-27T13:32:01.713906+00:00" + } + }, + "accounting": { + "inner_only_balance_seconds": 0.390625, + "inner_only_balance_percent": 4.62962962962963, + "child_outside_inner_seconds": 0.078125, + "runner_cpu_seconds": 0.0, + "unassigned_balance_seconds": 0.3125, + "unassigned_balance_percent": 3.7037037037037037 + }, + "state_fnv1a64": "6154007251599136798" + } + ], + "archive": { + "bytes": 123388, + "sha256": "263a3bf8e229b89f1e443b12e522daf69d30de403affa0dc49fe674c0dd8f241", + "files": 75, + "inventory_files": 74, + "byte_exact_streams": 24 + }, + "source_checkout_forms": { + "experiments/candidates/alacritty/Cargo.toml": "git_blob_lf_to_crlf", + "experiments/candidates/alacritty/src/measure.rs": "git_blob_lf_to_crlf", + "experiments/measure/run.py": "git_blob_bytes", + "experiments/measure/r54_contract.py": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/Cargo.lock": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/Cargo.toml": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/PROVENANCE.md": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/README.md": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/include/termforge_candidate.h": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-24-windows/summary.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-24-windows/validation.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-1/summary.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/candidate.jsonl": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/candidate.stderr.txt": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/chunks.txt": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/input.bin": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/report.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/results/2026-10-25-termforge-2/summary.json": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/run.py": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/src/ffi.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/src/main.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/src/measure.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/test_candidate.py": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/CHANGELOG.md": "git_blob_bytes", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/LICENSE-APACHE": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/event.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/event_loop.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/mod.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/storage.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/tests.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/index.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/lib.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/selection.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/sync.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/color.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/search.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/thread.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/mod.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/unix.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/blocking.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/child.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/conpty.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/mod.rs": "git_blob_lf_to_crlf", + "baseline/experiments/candidates/alacritty/vendor/alacritty_terminal/src/vi_mode.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/Cargo.lock": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/Cargo.toml": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/PROVENANCE.md": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/README.md": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/grapheme_checks.py": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/include/termforge_candidate.h": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-24-windows/summary.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-24-windows/validation.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/alternate-query/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/bounded-history/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-byte/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/fragmentation-whole/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-1/summary.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/alternate-query/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/bounded-history/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-byte/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/candidate.jsonl": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/candidate.stderr.txt": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/chunks.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/input.bin": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/fragmentation-whole/report.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/results/2026-10-25-termforge-2/summary.json": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/run.py": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/src/ffi.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/src/main.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/src/measure.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/test_candidate.py": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/CHANGELOG.md": "git_blob_bytes", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/Cargo.toml": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/LICENSE-APACHE": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/event.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/event_loop.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/mod.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/resize.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/row.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/storage.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/grid/tests.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/index.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/lib.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/selection.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/sync.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/cell.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/color.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/grapheme.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/mod.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/term/search.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/thread.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/mod.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/unix.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/blocking.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/child.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/conpty.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/tty/windows/mod.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/src/vi_mode.rs": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/GraphemeBreakTest-17.0.0.txt": "git_blob_lf_to_crlf", + "candidate/experiments/candidates/alacritty/vendor/alacritty_terminal/tests/data/UNICODE-LICENSE.txt": "git_blob_lf_to_crlf" + }, + "sanity_oracles": 4, + "observation_oracles": 6, + "formal_cost_samples": 0, + "performance_gate": "not_evaluated", + "old_hosted_event_cause": "unknown", + "source_tree": "86e2342d16acd101fe182793d20aef3f1f017633", + "provider": { + "run": 40512310499, + "attempt": 1, + "job": 122217071656, + "event": "push", + "branch": "perf/grapheme-cpu-hosted-r54-entry3", + "source": "4772f4f937c4f0f220be1630592f84bed3a591c8", + "status": "completed", + "conclusion": "success", + "labels": [ + "windows-2025" + ], + "started_at": "2026-10-27T13:28:22Z", + "completed_at": "2026-10-27T13:32:07Z", + "steps": [ + { + "number": 1, + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:23Z", + "completed_at": "2026-10-27T13:28:24Z" + }, + { + "number": 2, + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:24Z", + "completed_at": "2026-10-27T13:28:33Z" + }, + { + "number": 3, + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:33Z", + "completed_at": "2026-10-27T13:28:33Z" + }, + { + "number": 4, + "name": "Install fixed CPU observer", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:33Z", + "completed_at": "2026-10-27T13:28:40Z" + }, + { + "number": 5, + "name": "Select fixed compiler", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:40Z", + "completed_at": "2026-10-27T13:28:49Z" + }, + { + "number": 6, + "name": "Verify build, accounting and publication boundaries", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:28:49Z", + "completed_at": "2026-10-27T13:29:04Z" + }, + { + "number": 7, + "name": "Build audited pair and execute one six-observation diagnosis", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:29:04Z", + "completed_at": "2026-10-27T13:32:02Z" + }, + { + "number": 8, + "name": "Prepare the synthetic artifact whitelist", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:32:02Z", + "completed_at": "2026-10-27T13:32:03Z" + }, + { + "number": 9, + "name": "Preserve complete or failed synthetic evidence", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:32:03Z", + "completed_at": "2026-10-27T13:32:04Z" + }, + { + "number": 17, + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:32:04Z", + "completed_at": "2026-10-27T13:32:04Z" + }, + { + "number": 18, + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:32:04Z", + "completed_at": "2026-10-27T13:32:06Z" + }, + { + "number": 19, + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "started_at": "2026-10-27T13:32:06Z", + "completed_at": "2026-10-27T13:32:06Z" + } + ], + "artifact_id": 12738430576, + "artifact_digest": "sha256:263a3bf8e229b89f1e443b12e522daf69d30de403affa0dc49fe674c0dd8f241", + "image": "windows-2025-vs2026", + "image_version": "20261021.278.1" + }, + "previous_entries": [ + { + "source": "eed0ab7bae5fcc5d05a25cea5161dabc4f935878", + "run": 40509157581, + "job": 122206160085, + "attempt": 1, + "result": "preparation_test_path_boundary_failure", + "preparation_tests": 31, + "errors": 1, + "builds": 0, + "observations": 0, + "actual_temp_path_form": "not_recorded" + }, + { + "source": "c14adb9a7fa446476ad5c95c1f8c1b139ceb5ac4", + "branch": "perf/grapheme-cpu-hosted-r54-setup2", + "default_run": 40511533857, + "result": "no_cpu_workflow_run_listed", + "observations": 0, + "actual_push_file_list": "not_recorded" + } + ], + "local_tests": { + "measurement": { + "tests": 40, + "seconds": 19.436 + }, + "entry3_boundary": { + "tests": 4, + "seconds": 0.297 + }, + "hosted_preparation": { + "tests": 31, + "seconds": 14.042 + } + }, + "initial_ci": { + "40509376603": { + "jobs": [ + { + "id": 122206943124, + "name": "alacritty-headless (windows-2025, experiments/candidates/alacritty/target/release/termforge-alacr...", + "conclusion": "failure", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:08:09Z", + "completed_at": "2026-10-27T13:08:10Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:08:10Z", + "completed_at": "2026-10-27T13:08:16Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:08:16Z", + "completed_at": "2026-10-27T13:08:16Z" + }, + { + "name": "Install fixed semantic references", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:08:16Z", + "completed_at": "2026-10-27T13:08:22Z" + }, + { + "name": "Install fixed CPU observer", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:08:22Z", + "completed_at": "2026-10-27T13:08:25Z" + }, + { + "name": "Select fixed Rust toolchain", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:08:25Z", + "completed_at": "2026-10-27T13:08:34Z" + }, + { + "name": "Build locked candidate", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:08:34Z", + "completed_at": "2026-10-27T13:09:17Z" + }, + { + "name": "Test Rust projection", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:09:17Z", + "completed_at": "2026-10-27T13:09:33Z" + }, + { + "name": "Test fixed upstream and compatibility patches", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:09:33Z", + "completed_at": "2026-10-27T13:09:50Z" + }, + { + "name": "Build candidate measurement", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:09:50Z", + "completed_at": "2026-10-27T13:09:51Z" + }, + { + "name": "Build candidate measurement without allocator wrapper", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:09:51Z", + "completed_at": "2026-10-27T13:10:22Z" + }, + { + "name": "Configure native trace on Windows", + "status": "completed", + "conclusion": "success", + "number": 12, + "started_at": "2026-10-27T13:10:22Z", + "completed_at": "2026-10-27T13:10:33Z" + }, + { + "name": "Configure native trace on Unix", + "status": "completed", + "conclusion": "skipped", + "number": 13, + "started_at": "2026-10-27T13:10:33Z", + "completed_at": "2026-10-27T13:10:33Z" + }, + { + "name": "Build native trace", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:10:33Z", + "completed_at": "2026-10-27T13:10:37Z" + }, + { + "name": "Build native measurement", + "status": "completed", + "conclusion": "success", + "number": 15, + "started_at": "2026-10-27T13:10:37Z", + "completed_at": "2026-10-27T13:10:41Z" + }, + { + "name": "Build and test candidate C++ adapter", + "status": "completed", + "conclusion": "success", + "number": 16, + "started_at": "2026-10-27T13:10:41Z", + "completed_at": "2026-10-27T13:10:49Z" + }, + { + "name": "Build and test candidate shell on Windows", + "status": "completed", + "conclusion": "success", + "number": 17, + "started_at": "2026-10-27T13:10:49Z", + "completed_at": "2026-10-27T13:11:35Z" + }, + { + "name": "Verify empty-title GUI and UTF-8 conversion on Windows", + "status": "completed", + "conclusion": "success", + "number": 18, + "started_at": "2026-10-27T13:11:35Z", + "completed_at": "2026-10-27T13:12:03Z" + }, + { + "name": "Build real application capture on Windows", + "status": "completed", + "conclusion": "success", + "number": 19, + "started_at": "2026-10-27T13:12:03Z", + "completed_at": "2026-10-27T13:12:07Z" + }, + { + "name": "Run headless integration checks", + "status": "completed", + "conclusion": "success", + "number": 20, + "started_at": "2026-10-27T13:12:07Z", + "completed_at": "2026-10-27T13:12:08Z" + }, + { + "name": "Test runner and failure gates", + "status": "completed", + "conclusion": "success", + "number": 21, + "started_at": "2026-10-27T13:12:08Z", + "completed_at": "2026-10-27T13:12:08Z" + }, + { + "name": "Compare common native traces", + "status": "completed", + "conclusion": "success", + "number": 22, + "started_at": "2026-10-27T13:12:08Z", + "completed_at": "2026-10-27T13:12:11Z" + }, + { + "name": "Test common comparison and failure gates", + "status": "completed", + "conclusion": "success", + "number": 23, + "started_at": "2026-10-27T13:12:11Z", + "completed_at": "2026-10-27T13:12:14Z" + }, + { + "name": "Compare extended capability traces", + "status": "completed", + "conclusion": "success", + "number": 24, + "started_at": "2026-10-27T13:12:14Z", + "completed_at": "2026-10-27T13:12:15Z" + }, + { + "name": "Test extended capabilities and unsupported gates", + "status": "completed", + "conclusion": "success", + "number": 25, + "started_at": "2026-10-27T13:12:15Z", + "completed_at": "2026-10-27T13:12:16Z" + }, + { + "name": "Compare real application traces on Windows", + "status": "completed", + "conclusion": "success", + "number": 26, + "started_at": "2026-10-27T13:12:16Z", + "completed_at": "2026-10-27T13:12:17Z" + }, + { + "name": "Test real application traces on Windows", + "status": "completed", + "conclusion": "success", + "number": 27, + "started_at": "2026-10-27T13:12:17Z", + "completed_at": "2026-10-27T13:12:29Z" + }, + { + "name": "Run bounded recovery fuzz", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:12:29Z", + "completed_at": "2026-10-27T13:12:29Z" + }, + { + "name": "Test fuzz minimization and fixed regressions", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:12:29Z", + "completed_at": "2026-10-27T13:12:31Z" + }, + { + "name": "Compare interleaved project costs", + "status": "completed", + "conclusion": "success", + "number": 30, + "started_at": "2026-10-27T13:12:31Z", + "completed_at": "2026-10-27T13:13:02Z" + }, + { + "name": "Compare interleaved interactive costs", + "status": "completed", + "conclusion": "success", + "number": 31, + "started_at": "2026-10-27T13:13:02Z", + "completed_at": "2026-10-27T13:13:42Z" + }, + { + "name": "Test measurement contracts", + "status": "completed", + "conclusion": "failure", + "number": 32, + "started_at": "2026-10-27T13:13:42Z", + "completed_at": "2026-10-27T13:14:15Z" + }, + { + "name": "Preserve synthetic candidate evidence", + "status": "completed", + "conclusion": "success", + "number": 33, + "started_at": "2026-10-27T13:14:15Z", + "completed_at": "2026-10-27T13:14:19Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "skipped", + "number": 65, + "started_at": "2026-10-27T13:14:19Z", + "completed_at": "2026-10-27T13:14:19Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 66, + "started_at": "2026-10-27T13:14:19Z", + "completed_at": "2026-10-27T13:14:22Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 67, + "started_at": "2026-10-27T13:14:22Z", + "completed_at": "2026-10-27T13:14:22Z" + } + ], + "log_evidence": [ + "2026-10-27T13:12:08.8322257Z Ran 6 tests in 0.149s", + "2026-10-27T13:12:14.1163493Z Ran 4 tests in 2.122s", + "2026-10-27T13:12:16.1916158Z Ran 4 tests in 0.522s", + "2026-10-27T13:12:28.9034935Z Ran 8 tests in 10.796s", + "2026-10-27T13:12:30.9126353Z Ran 4 tests in 0.716s", + "2026-10-27T13:14:15.3632402Z ERROR: test_publication_keeps_failed_streams_and_separates_the_six_observation_scope (test_r54_cpu_hosted.CpuHostedBoundaryTests.test_publication_keeps_failed_streams_and_separates_the_six_observation_scope)", + "2026-10-27T13:14:15.3646222Z ValueError: publication paths must stay in workspace local", + "2026-10-27T13:14:15.3646942Z Ran 49 tests in 31.869s", + "2026-10-27T13:14:15.3647151Z FAILED (errors=1)" + ] + }, + { + "id": 122206943421, + "name": "alacritty-headless (ubuntu-24.04, experiments/candidates/alacritty/target/release/termforge-alacr...", + "conclusion": "success", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:08:10Z", + "completed_at": "2026-10-27T13:08:11Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:08:11Z", + "completed_at": "2026-10-27T13:08:13Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:08:13Z", + "completed_at": "2026-10-27T13:08:13Z" + }, + { + "name": "Install fixed semantic references", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:08:13Z", + "completed_at": "2026-10-27T13:08:14Z" + }, + { + "name": "Install fixed CPU observer", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:08:14Z", + "completed_at": "2026-10-27T13:08:15Z" + }, + { + "name": "Select fixed Rust toolchain", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:08:15Z", + "completed_at": "2026-10-27T13:08:22Z" + }, + { + "name": "Build locked candidate", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:08:22Z", + "completed_at": "2026-10-27T13:08:43Z" + }, + { + "name": "Test Rust projection", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:08:43Z", + "completed_at": "2026-10-27T13:08:52Z" + }, + { + "name": "Test fixed upstream and compatibility patches", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:08:52Z", + "completed_at": "2026-10-27T13:09:03Z" + }, + { + "name": "Build candidate measurement", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:09:03Z", + "completed_at": "2026-10-27T13:09:03Z" + }, + { + "name": "Build candidate measurement without allocator wrapper", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:09:03Z", + "completed_at": "2026-10-27T13:09:21Z" + }, + { + "name": "Configure native trace on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 12, + "started_at": "2026-10-27T13:09:21Z", + "completed_at": "2026-10-27T13:09:21Z" + }, + { + "name": "Configure native trace on Unix", + "status": "completed", + "conclusion": "success", + "number": 13, + "started_at": "2026-10-27T13:09:21Z", + "completed_at": "2026-10-27T13:09:23Z" + }, + { + "name": "Build native trace", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:09:23Z", + "completed_at": "2026-10-27T13:09:24Z" + }, + { + "name": "Build native measurement", + "status": "completed", + "conclusion": "success", + "number": 15, + "started_at": "2026-10-27T13:09:24Z", + "completed_at": "2026-10-27T13:09:27Z" + }, + { + "name": "Build and test candidate C++ adapter", + "status": "completed", + "conclusion": "success", + "number": 16, + "started_at": "2026-10-27T13:09:27Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Build and test candidate shell on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 17, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Verify empty-title GUI and UTF-8 conversion on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 18, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Build real application capture on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 19, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Run headless integration checks", + "status": "completed", + "conclusion": "success", + "number": 20, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Test runner and failure gates", + "status": "completed", + "conclusion": "success", + "number": 21, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Compare common native traces", + "status": "completed", + "conclusion": "success", + "number": 22, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:09:35Z" + }, + { + "name": "Test common comparison and failure gates", + "status": "completed", + "conclusion": "success", + "number": 23, + "started_at": "2026-10-27T13:09:35Z", + "completed_at": "2026-10-27T13:09:36Z" + }, + { + "name": "Compare extended capability traces", + "status": "completed", + "conclusion": "success", + "number": 24, + "started_at": "2026-10-27T13:09:36Z", + "completed_at": "2026-10-27T13:09:36Z" + }, + { + "name": "Test extended capabilities and unsupported gates", + "status": "completed", + "conclusion": "success", + "number": 25, + "started_at": "2026-10-27T13:09:36Z", + "completed_at": "2026-10-27T13:09:37Z" + }, + { + "name": "Compare real application traces on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 26, + "started_at": "2026-10-27T13:09:37Z", + "completed_at": "2026-10-27T13:09:37Z" + }, + { + "name": "Test real application traces on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 27, + "started_at": "2026-10-27T13:09:37Z", + "completed_at": "2026-10-27T13:09:37Z" + }, + { + "name": "Run bounded recovery fuzz", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:09:37Z", + "completed_at": "2026-10-27T13:09:37Z" + }, + { + "name": "Test fuzz minimization and fixed regressions", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:09:37Z", + "completed_at": "2026-10-27T13:09:37Z" + }, + { + "name": "Compare interleaved project costs", + "status": "completed", + "conclusion": "success", + "number": 30, + "started_at": "2026-10-27T13:09:37Z", + "completed_at": "2026-10-27T13:10:06Z" + }, + { + "name": "Compare interleaved interactive costs", + "status": "completed", + "conclusion": "success", + "number": 31, + "started_at": "2026-10-27T13:10:06Z", + "completed_at": "2026-10-27T13:10:45Z" + }, + { + "name": "Test measurement contracts", + "status": "completed", + "conclusion": "success", + "number": 32, + "started_at": "2026-10-27T13:10:45Z", + "completed_at": "2026-10-27T13:11:11Z" + }, + { + "name": "Preserve synthetic candidate evidence", + "status": "completed", + "conclusion": "success", + "number": 33, + "started_at": "2026-10-27T13:11:11Z", + "completed_at": "2026-10-27T13:11:14Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 65, + "started_at": "2026-10-27T13:11:14Z", + "completed_at": "2026-10-27T13:11:14Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 66, + "started_at": "2026-10-27T13:11:14Z", + "completed_at": "2026-10-27T13:11:15Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 67, + "started_at": "2026-10-27T13:11:15Z", + "completed_at": "2026-10-27T13:11:15Z" + } + ], + "log_evidence": [] + } + ] + }, + "40509377108": { + "jobs": [ + { + "id": 122206924548, + "name": "regression (windows-2025, build/bin/Release)", + "conclusion": "failure", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:08:07Z", + "completed_at": "2026-10-27T13:08:08Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:08:08Z", + "completed_at": "2026-10-27T13:08:13Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:08:13Z", + "completed_at": "2026-10-27T13:08:13Z" + }, + { + "name": "Install test dependencies", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:08:13Z", + "completed_at": "2026-10-27T13:08:20Z" + }, + { + "name": "Configure", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:08:20Z", + "completed_at": "2026-10-27T13:08:35Z" + }, + { + "name": "Build", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:08:35Z", + "completed_at": "2026-10-27T13:09:33Z" + }, + { + "name": "Verify shell preparation guards and oracles", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:09:33Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Prepare hosted Windows shell prerequisites", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:10:14Z" + }, + { + "name": "Preserve sanitized shell preparation result", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:10:14Z", + "completed_at": "2026-10-27T13:10:16Z" + }, + { + "name": "Test", + "status": "completed", + "conclusion": "failure", + "number": 10, + "started_at": "2026-10-27T13:10:16Z", + "completed_at": "2026-10-27T13:14:29Z" + }, + { + "name": "Preserve clipboard and window integration evidence", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:14:29Z", + "completed_at": "2026-10-27T13:14:34Z" + }, + { + "name": "Full ASCII state replay", + "status": "completed", + "conclusion": "skipped", + "number": 12, + "started_at": "2026-10-27T13:14:34Z", + "completed_at": "2026-10-27T13:14:34Z" + }, + { + "name": "Full Unicode state replay", + "status": "completed", + "conclusion": "skipped", + "number": 13, + "started_at": "2026-10-27T13:14:34Z", + "completed_at": "2026-10-27T13:14:34Z" + }, + { + "name": "Preserve synthetic failure evidence", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:14:34Z", + "completed_at": "2026-10-27T13:14:39Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "skipped", + "number": 27, + "started_at": "2026-10-27T13:14:39Z", + "completed_at": "2026-10-27T13:14:39Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:14:39Z", + "completed_at": "2026-10-27T13:14:41Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:14:41Z", + "completed_at": "2026-10-27T13:14:41Z" + } + ], + "log_evidence": [ + "2026-10-27T13:09:34.6075054Z Ran 23 tests in 0.141s", + "2026-10-27T13:10:42.1081064Z ERROR: test_publication_keeps_failed_streams_and_separates_the_six_observation_scope (experiments.measure.test_r54_cpu_hosted.CpuHostedBoundaryTests.test_publication_keeps_failed_streams_and_separates_the_six_observation_scope)", + "2026-10-27T13:10:42.1091660Z ValueError: publication paths must stay in workspace local", + "2026-10-27T13:10:42.1092267Z Ran 40 tests in 14.741s", + "2026-10-27T13:10:42.1092447Z FAILED (errors=1)", + "2026-10-27T13:14:29.5651977Z 98% tests passed, 1 tests failed out of 46" + ] + }, + { + "id": 122206924940, + "name": "regression (ubuntu-24.04, build/bin)", + "conclusion": "success", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:08:07Z", + "completed_at": "2026-10-27T13:08:09Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:08:09Z", + "completed_at": "2026-10-27T13:08:11Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:08:11Z", + "completed_at": "2026-10-27T13:08:11Z" + }, + { + "name": "Install test dependencies", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:08:11Z", + "completed_at": "2026-10-27T13:08:13Z" + }, + { + "name": "Configure", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:08:13Z", + "completed_at": "2026-10-27T13:08:14Z" + }, + { + "name": "Build", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:08:14Z", + "completed_at": "2026-10-27T13:08:27Z" + }, + { + "name": "Verify shell preparation guards and oracles", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:08:27Z", + "completed_at": "2026-10-27T13:08:28Z" + }, + { + "name": "Prepare hosted Windows shell prerequisites", + "status": "completed", + "conclusion": "skipped", + "number": 8, + "started_at": "2026-10-27T13:08:28Z", + "completed_at": "2026-10-27T13:08:28Z" + }, + { + "name": "Preserve sanitized shell preparation result", + "status": "completed", + "conclusion": "skipped", + "number": 9, + "started_at": "2026-10-27T13:08:28Z", + "completed_at": "2026-10-27T13:08:28Z" + }, + { + "name": "Test", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:08:28Z", + "completed_at": "2026-10-27T13:08:50Z" + }, + { + "name": "Preserve clipboard and window integration evidence", + "status": "completed", + "conclusion": "skipped", + "number": 11, + "started_at": "2026-10-27T13:08:50Z", + "completed_at": "2026-10-27T13:08:50Z" + }, + { + "name": "Full ASCII state replay", + "status": "completed", + "conclusion": "success", + "number": 12, + "started_at": "2026-10-27T13:08:50Z", + "completed_at": "2026-10-27T13:08:50Z" + }, + { + "name": "Full Unicode state replay", + "status": "completed", + "conclusion": "success", + "number": 13, + "started_at": "2026-10-27T13:08:50Z", + "completed_at": "2026-10-27T13:08:50Z" + }, + { + "name": "Preserve synthetic failure evidence", + "status": "completed", + "conclusion": "skipped", + "number": 14, + "started_at": "2026-10-27T13:08:50Z", + "completed_at": "2026-10-27T13:08:50Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 27, + "started_at": "2026-10-27T13:08:50Z", + "completed_at": "2026-10-27T13:08:51Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:08:51Z", + "completed_at": "2026-10-27T13:08:51Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:08:51Z", + "completed_at": "2026-10-27T13:08:51Z" + } + ], + "log_evidence": [] + } + ] + }, + "40509157544": { + "jobs": [ + { + "id": 122206159254, + "name": "alacritty-headless (windows-2025, experiments/candidates/alacritty/target/release/termforge-alacr...", + "conclusion": "failure", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:06:32Z", + "completed_at": "2026-10-27T13:06:33Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:06:33Z", + "completed_at": "2026-10-27T13:06:39Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:06:39Z", + "completed_at": "2026-10-27T13:06:39Z" + }, + { + "name": "Install fixed semantic references", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:06:39Z", + "completed_at": "2026-10-27T13:06:45Z" + }, + { + "name": "Install fixed CPU observer", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:06:45Z", + "completed_at": "2026-10-27T13:06:47Z" + }, + { + "name": "Select fixed Rust toolchain", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:06:47Z", + "completed_at": "2026-10-27T13:06:57Z" + }, + { + "name": "Build locked candidate", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:06:57Z", + "completed_at": "2026-10-27T13:07:28Z" + }, + { + "name": "Test Rust projection", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:07:28Z", + "completed_at": "2026-10-27T13:07:41Z" + }, + { + "name": "Test fixed upstream and compatibility patches", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:07:41Z", + "completed_at": "2026-10-27T13:07:53Z" + }, + { + "name": "Build candidate measurement", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:07:53Z", + "completed_at": "2026-10-27T13:07:54Z" + }, + { + "name": "Build candidate measurement without allocator wrapper", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:07:54Z", + "completed_at": "2026-10-27T13:08:15Z" + }, + { + "name": "Configure native trace on Windows", + "status": "completed", + "conclusion": "success", + "number": 12, + "started_at": "2026-10-27T13:08:15Z", + "completed_at": "2026-10-27T13:08:25Z" + }, + { + "name": "Configure native trace on Unix", + "status": "completed", + "conclusion": "skipped", + "number": 13, + "started_at": "2026-10-27T13:08:25Z", + "completed_at": "2026-10-27T13:08:25Z" + }, + { + "name": "Build native trace", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:08:25Z", + "completed_at": "2026-10-27T13:08:29Z" + }, + { + "name": "Build native measurement", + "status": "completed", + "conclusion": "success", + "number": 15, + "started_at": "2026-10-27T13:08:29Z", + "completed_at": "2026-10-27T13:08:32Z" + }, + { + "name": "Build and test candidate C++ adapter", + "status": "completed", + "conclusion": "success", + "number": 16, + "started_at": "2026-10-27T13:08:32Z", + "completed_at": "2026-10-27T13:08:39Z" + }, + { + "name": "Build and test candidate shell on Windows", + "status": "completed", + "conclusion": "success", + "number": 17, + "started_at": "2026-10-27T13:08:39Z", + "completed_at": "2026-10-27T13:09:22Z" + }, + { + "name": "Verify empty-title GUI and UTF-8 conversion on Windows", + "status": "completed", + "conclusion": "success", + "number": 18, + "started_at": "2026-10-27T13:09:22Z", + "completed_at": "2026-10-27T13:09:43Z" + }, + { + "name": "Build real application capture on Windows", + "status": "completed", + "conclusion": "success", + "number": 19, + "started_at": "2026-10-27T13:09:43Z", + "completed_at": "2026-10-27T13:09:46Z" + }, + { + "name": "Run headless integration checks", + "status": "completed", + "conclusion": "success", + "number": 20, + "started_at": "2026-10-27T13:09:46Z", + "completed_at": "2026-10-27T13:09:46Z" + }, + { + "name": "Test runner and failure gates", + "status": "completed", + "conclusion": "success", + "number": 21, + "started_at": "2026-10-27T13:09:46Z", + "completed_at": "2026-10-27T13:09:47Z" + }, + { + "name": "Compare common native traces", + "status": "completed", + "conclusion": "success", + "number": 22, + "started_at": "2026-10-27T13:09:47Z", + "completed_at": "2026-10-27T13:09:49Z" + }, + { + "name": "Test common comparison and failure gates", + "status": "completed", + "conclusion": "success", + "number": 23, + "started_at": "2026-10-27T13:09:49Z", + "completed_at": "2026-10-27T13:09:51Z" + }, + { + "name": "Compare extended capability traces", + "status": "completed", + "conclusion": "success", + "number": 24, + "started_at": "2026-10-27T13:09:51Z", + "completed_at": "2026-10-27T13:09:52Z" + }, + { + "name": "Test extended capabilities and unsupported gates", + "status": "completed", + "conclusion": "success", + "number": 25, + "started_at": "2026-10-27T13:09:52Z", + "completed_at": "2026-10-27T13:09:52Z" + }, + { + "name": "Compare real application traces on Windows", + "status": "completed", + "conclusion": "success", + "number": 26, + "started_at": "2026-10-27T13:09:52Z", + "completed_at": "2026-10-27T13:09:53Z" + }, + { + "name": "Test real application traces on Windows", + "status": "completed", + "conclusion": "success", + "number": 27, + "started_at": "2026-10-27T13:09:53Z", + "completed_at": "2026-10-27T13:10:05Z" + }, + { + "name": "Run bounded recovery fuzz", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:10:05Z", + "completed_at": "2026-10-27T13:10:05Z" + }, + { + "name": "Test fuzz minimization and fixed regressions", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:10:05Z", + "completed_at": "2026-10-27T13:10:06Z" + }, + { + "name": "Compare interleaved project costs", + "status": "completed", + "conclusion": "success", + "number": 30, + "started_at": "2026-10-27T13:10:06Z", + "completed_at": "2026-10-27T13:10:36Z" + }, + { + "name": "Compare interleaved interactive costs", + "status": "completed", + "conclusion": "success", + "number": 31, + "started_at": "2026-10-27T13:10:36Z", + "completed_at": "2026-10-27T13:11:16Z" + }, + { + "name": "Test measurement contracts", + "status": "completed", + "conclusion": "failure", + "number": 32, + "started_at": "2026-10-27T13:11:16Z", + "completed_at": "2026-10-27T13:11:44Z" + }, + { + "name": "Preserve synthetic candidate evidence", + "status": "completed", + "conclusion": "success", + "number": 33, + "started_at": "2026-10-27T13:11:44Z", + "completed_at": "2026-10-27T13:11:49Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "skipped", + "number": 65, + "started_at": "2026-10-27T13:11:49Z", + "completed_at": "2026-10-27T13:11:49Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 66, + "started_at": "2026-10-27T13:11:49Z", + "completed_at": "2026-10-27T13:11:50Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 67, + "started_at": "2026-10-27T13:11:50Z", + "completed_at": "2026-10-27T13:11:50Z" + } + ], + "log_evidence": [ + "2026-10-27T13:09:47.1510883Z Ran 6 tests in 0.112s", + "2026-10-27T13:09:51.1475615Z Ran 4 tests in 1.624s", + "2026-10-27T13:09:52.7038066Z Ran 4 tests in 0.401s", + "2026-10-27T13:10:04.9281496Z Ran 8 tests in 10.681s", + "2026-10-27T13:10:06.4638361Z Ran 4 tests in 0.571s", + "2026-10-27T13:11:44.7874775Z ERROR: test_publication_keeps_failed_streams_and_separates_the_six_observation_scope (test_r54_cpu_hosted.CpuHostedBoundaryTests.test_publication_keeps_failed_streams_and_separates_the_six_observation_scope)", + "2026-10-27T13:11:44.7884371Z ValueError: publication paths must stay in workspace local", + "2026-10-27T13:11:44.7884990Z Ran 49 tests in 28.223s", + "2026-10-27T13:11:44.7885202Z FAILED (errors=1)" + ] + }, + { + "id": 122206159638, + "name": "alacritty-headless (ubuntu-24.04, experiments/candidates/alacritty/target/release/termforge-alacr...", + "conclusion": "success", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:06:33Z", + "completed_at": "2026-10-27T13:06:34Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:06:34Z", + "completed_at": "2026-10-27T13:06:36Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:06:36Z", + "completed_at": "2026-10-27T13:06:36Z" + }, + { + "name": "Install fixed semantic references", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:06:36Z", + "completed_at": "2026-10-27T13:06:37Z" + }, + { + "name": "Install fixed CPU observer", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:06:37Z", + "completed_at": "2026-10-27T13:06:38Z" + }, + { + "name": "Select fixed Rust toolchain", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:06:38Z", + "completed_at": "2026-10-27T13:06:46Z" + }, + { + "name": "Build locked candidate", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:06:46Z", + "completed_at": "2026-10-27T13:07:08Z" + }, + { + "name": "Test Rust projection", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:07:08Z", + "completed_at": "2026-10-27T13:07:18Z" + }, + { + "name": "Test fixed upstream and compatibility patches", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:07:18Z", + "completed_at": "2026-10-27T13:07:30Z" + }, + { + "name": "Build candidate measurement", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:07:30Z", + "completed_at": "2026-10-27T13:07:30Z" + }, + { + "name": "Build candidate measurement without allocator wrapper", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:07:30Z", + "completed_at": "2026-10-27T13:07:48Z" + }, + { + "name": "Configure native trace on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 12, + "started_at": "2026-10-27T13:07:48Z", + "completed_at": "2026-10-27T13:07:48Z" + }, + { + "name": "Configure native trace on Unix", + "status": "completed", + "conclusion": "success", + "number": 13, + "started_at": "2026-10-27T13:07:48Z", + "completed_at": "2026-10-27T13:07:51Z" + }, + { + "name": "Build native trace", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:07:51Z", + "completed_at": "2026-10-27T13:07:54Z" + }, + { + "name": "Build native measurement", + "status": "completed", + "conclusion": "success", + "number": 15, + "started_at": "2026-10-27T13:07:54Z", + "completed_at": "2026-10-27T13:07:56Z" + }, + { + "name": "Build and test candidate C++ adapter", + "status": "completed", + "conclusion": "success", + "number": 16, + "started_at": "2026-10-27T13:07:56Z", + "completed_at": "2026-10-27T13:08:03Z" + }, + { + "name": "Build and test candidate shell on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 17, + "started_at": "2026-10-27T13:08:03Z", + "completed_at": "2026-10-27T13:08:03Z" + }, + { + "name": "Verify empty-title GUI and UTF-8 conversion on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 18, + "started_at": "2026-10-27T13:08:03Z", + "completed_at": "2026-10-27T13:08:03Z" + }, + { + "name": "Build real application capture on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 19, + "started_at": "2026-10-27T13:08:03Z", + "completed_at": "2026-10-27T13:08:03Z" + }, + { + "name": "Run headless integration checks", + "status": "completed", + "conclusion": "success", + "number": 20, + "started_at": "2026-10-27T13:08:03Z", + "completed_at": "2026-10-27T13:08:03Z" + }, + { + "name": "Test runner and failure gates", + "status": "completed", + "conclusion": "success", + "number": 21, + "started_at": "2026-10-27T13:08:03Z", + "completed_at": "2026-10-27T13:08:04Z" + }, + { + "name": "Compare common native traces", + "status": "completed", + "conclusion": "success", + "number": 22, + "started_at": "2026-10-27T13:08:04Z", + "completed_at": "2026-10-27T13:08:04Z" + }, + { + "name": "Test common comparison and failure gates", + "status": "completed", + "conclusion": "success", + "number": 23, + "started_at": "2026-10-27T13:08:04Z", + "completed_at": "2026-10-27T13:08:05Z" + }, + { + "name": "Compare extended capability traces", + "status": "completed", + "conclusion": "success", + "number": 24, + "started_at": "2026-10-27T13:08:05Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Test extended capabilities and unsupported gates", + "status": "completed", + "conclusion": "success", + "number": 25, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Compare real application traces on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 26, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Test real application traces on Windows", + "status": "completed", + "conclusion": "skipped", + "number": 27, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Run bounded recovery fuzz", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Test fuzz minimization and fixed regressions", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:06Z" + }, + { + "name": "Compare interleaved project costs", + "status": "completed", + "conclusion": "success", + "number": 30, + "started_at": "2026-10-27T13:08:06Z", + "completed_at": "2026-10-27T13:08:36Z" + }, + { + "name": "Compare interleaved interactive costs", + "status": "completed", + "conclusion": "success", + "number": 31, + "started_at": "2026-10-27T13:08:36Z", + "completed_at": "2026-10-27T13:09:16Z" + }, + { + "name": "Test measurement contracts", + "status": "completed", + "conclusion": "success", + "number": 32, + "started_at": "2026-10-27T13:09:16Z", + "completed_at": "2026-10-27T13:09:42Z" + }, + { + "name": "Preserve synthetic candidate evidence", + "status": "completed", + "conclusion": "success", + "number": 33, + "started_at": "2026-10-27T13:09:42Z", + "completed_at": "2026-10-27T13:09:45Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 65, + "started_at": "2026-10-27T13:09:45Z", + "completed_at": "2026-10-27T13:09:45Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 66, + "started_at": "2026-10-27T13:09:45Z", + "completed_at": "2026-10-27T13:09:45Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 67, + "started_at": "2026-10-27T13:09:45Z", + "completed_at": "2026-10-27T13:09:45Z" + } + ], + "log_evidence": [] + } + ] + }, + "40509157365": { + "jobs": [ + { + "id": 122206159312, + "name": "regression (ubuntu-24.04, build/bin)", + "conclusion": "success", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:06:32Z", + "completed_at": "2026-10-27T13:06:33Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:06:33Z", + "completed_at": "2026-10-27T13:06:34Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:06:34Z", + "completed_at": "2026-10-27T13:06:35Z" + }, + { + "name": "Install test dependencies", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:06:35Z", + "completed_at": "2026-10-27T13:06:37Z" + }, + { + "name": "Configure", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:06:37Z", + "completed_at": "2026-10-27T13:06:44Z" + }, + { + "name": "Build", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:06:44Z", + "completed_at": "2026-10-27T13:06:53Z" + }, + { + "name": "Verify shell preparation guards and oracles", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:06:53Z", + "completed_at": "2026-10-27T13:06:54Z" + }, + { + "name": "Prepare hosted Windows shell prerequisites", + "status": "completed", + "conclusion": "skipped", + "number": 8, + "started_at": "2026-10-27T13:06:54Z", + "completed_at": "2026-10-27T13:06:54Z" + }, + { + "name": "Preserve sanitized shell preparation result", + "status": "completed", + "conclusion": "skipped", + "number": 9, + "started_at": "2026-10-27T13:06:54Z", + "completed_at": "2026-10-27T13:06:54Z" + }, + { + "name": "Test", + "status": "completed", + "conclusion": "success", + "number": 10, + "started_at": "2026-10-27T13:06:54Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Preserve clipboard and window integration evidence", + "status": "completed", + "conclusion": "skipped", + "number": 11, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Full ASCII state replay", + "status": "completed", + "conclusion": "success", + "number": 12, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Full Unicode state replay", + "status": "completed", + "conclusion": "success", + "number": 13, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Preserve synthetic failure evidence", + "status": "completed", + "conclusion": "skipped", + "number": 14, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 27, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:07:11Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:07:11Z", + "completed_at": "2026-10-27T13:07:11Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:07:11Z", + "completed_at": "2026-10-27T13:07:11Z" + } + ], + "log_evidence": [] + }, + { + "id": 122206159781, + "name": "regression (windows-2025, build/bin/Release)", + "conclusion": "failure", + "steps": [ + { + "name": "Set up job", + "status": "completed", + "conclusion": "success", + "number": 1, + "started_at": "2026-10-27T13:06:33Z", + "completed_at": "2026-10-27T13:06:35Z" + }, + { + "name": "Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 2, + "started_at": "2026-10-27T13:06:35Z", + "completed_at": "2026-10-27T13:06:42Z" + }, + { + "name": "Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "success", + "number": 3, + "started_at": "2026-10-27T13:06:42Z", + "completed_at": "2026-10-27T13:06:42Z" + }, + { + "name": "Install test dependencies", + "status": "completed", + "conclusion": "success", + "number": 4, + "started_at": "2026-10-27T13:06:42Z", + "completed_at": "2026-10-27T13:06:53Z" + }, + { + "name": "Configure", + "status": "completed", + "conclusion": "success", + "number": 5, + "started_at": "2026-10-27T13:06:53Z", + "completed_at": "2026-10-27T13:07:10Z" + }, + { + "name": "Build", + "status": "completed", + "conclusion": "success", + "number": 6, + "started_at": "2026-10-27T13:07:10Z", + "completed_at": "2026-10-27T13:08:45Z" + }, + { + "name": "Verify shell preparation guards and oracles", + "status": "completed", + "conclusion": "success", + "number": 7, + "started_at": "2026-10-27T13:08:45Z", + "completed_at": "2026-10-27T13:08:47Z" + }, + { + "name": "Prepare hosted Windows shell prerequisites", + "status": "completed", + "conclusion": "success", + "number": 8, + "started_at": "2026-10-27T13:08:47Z", + "completed_at": "2026-10-27T13:09:33Z" + }, + { + "name": "Preserve sanitized shell preparation result", + "status": "completed", + "conclusion": "success", + "number": 9, + "started_at": "2026-10-27T13:09:33Z", + "completed_at": "2026-10-27T13:09:34Z" + }, + { + "name": "Test", + "status": "completed", + "conclusion": "failure", + "number": 10, + "started_at": "2026-10-27T13:09:34Z", + "completed_at": "2026-10-27T13:14:21Z" + }, + { + "name": "Preserve clipboard and window integration evidence", + "status": "completed", + "conclusion": "success", + "number": 11, + "started_at": "2026-10-27T13:14:21Z", + "completed_at": "2026-10-27T13:14:30Z" + }, + { + "name": "Full ASCII state replay", + "status": "completed", + "conclusion": "skipped", + "number": 12, + "started_at": "2026-10-27T13:14:30Z", + "completed_at": "2026-10-27T13:14:30Z" + }, + { + "name": "Full Unicode state replay", + "status": "completed", + "conclusion": "skipped", + "number": 13, + "started_at": "2026-10-27T13:14:30Z", + "completed_at": "2026-10-27T13:14:30Z" + }, + { + "name": "Preserve synthetic failure evidence", + "status": "completed", + "conclusion": "success", + "number": 14, + "started_at": "2026-10-27T13:14:30Z", + "completed_at": "2026-10-27T13:14:38Z" + }, + { + "name": "Post Run actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0", + "status": "completed", + "conclusion": "skipped", + "number": 27, + "started_at": "2026-10-27T13:14:38Z", + "completed_at": "2026-10-27T13:14:38Z" + }, + { + "name": "Post Run actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1", + "status": "completed", + "conclusion": "success", + "number": 28, + "started_at": "2026-10-27T13:14:38Z", + "completed_at": "2026-10-27T13:14:41Z" + }, + { + "name": "Complete job", + "status": "completed", + "conclusion": "success", + "number": 29, + "started_at": "2026-10-27T13:14:41Z", + "completed_at": "2026-10-27T13:14:41Z" + } + ], + "log_evidence": [ + "2026-10-27T13:08:46.9370598Z Ran 23 tests in 0.204s", + "2026-10-27T13:10:07.8942946Z ERROR: test_publication_keeps_failed_streams_and_separates_the_six_observation_scope (experiments.measure.test_r54_cpu_hosted.CpuHostedBoundaryTests.test_publication_keeps_failed_streams_and_separates_the_six_observation_scope)", + "2026-10-27T13:10:07.8959865Z ValueError: publication paths must stay in workspace local", + "2026-10-27T13:10:07.8961082Z Ran 40 tests in 16.284s", + "2026-10-27T13:10:07.8961443Z FAILED (errors=1)", + "2026-10-27T13:14:21.2079969Z 98% tests passed, 1 tests failed out of 46" + ] + } + ] + } + }, + "limits": [ + "No idle qualification, A/B cost ratio or performance acceptance.", + "Signed CPU residual includes possible guest activity, counter quantization and non-atomic endpoints; no process attribution.", + "Own outside-inner CPU does not explain all residual activity in this new run; old hosted cause remains unknown.", + "New binaries and host identity are independent of earlier local and hosted builds.", + "Full controlled batches, wrapper/projection/component attribution and user-confirmed budgets remain incomplete." + ] +} diff --git a/experiments/measure/results/2026-10-27-r54-cpu-hosted/verify.py b/experiments/measure/results/2026-10-27-r54-cpu-hosted/verify.py new file mode 100644 index 00000000..eff75a07 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-hosted/verify.py @@ -0,0 +1,177 @@ +"""Read retained evidence only; never build or launch a measurement.""" +import hashlib +import json +from pathlib import Path +import re +import subprocess +import sys +import zipfile + +ROOT = Path(__file__).resolve().parents[4] +sys.path.insert(0, str(ROOT)) +from experiments.measure.r54_contract import SOURCES, canonical, expected_sample, protocol, validate_sample +from experiments.measure.r54_inputs import IDENTITY, OVERLAYS +from experiments.measure.run import validate_result + +SOURCE = '4772f4f937c4f0f220be1630592f84bed3a591c8' +RUN = '40512310499' +ORDER = ('baseline', 'candidate', 'candidate', 'baseline', 'baseline', 'candidate') + +def digest(data): + return hashlib.sha256(data).hexdigest() + +def source_file_matches(commit, name, sha): + data = subprocess.check_output(['git','show',commit+':'+name],cwd=ROOT) + if digest(data) == sha: + return 'git_blob_bytes' + # Windows checkout can convert text without explicit eol attributes to CRLF. + # Require the complete captured SHA to match that exact conversion. + assert b'\0' not in data and b'\r\n' not in data, name + assert digest(data.replace(b'\n', b'\r\n')) == sha, name + return 'git_blob_lf_to_crlf' + +def main(path): + assert path.stat().st_size == 123388 + assert digest(path.read_bytes()) == '263a3bf8e229b89f1e443b12e522daf69d30de403affa0dc49fe674c0dd8f241' + with zipfile.ZipFile(path) as archive: + read = lambda name: json.loads(archive.read(name)) + publication = read('publication.json') + assert publication['schema'] == 'termforge-r54-cpu-hosted-publication-v1' + inventory = {v['path']: v for v in publication['artifacts']} + assert len(inventory) == len(publication['artifacts']) == 74 + assert len(archive.namelist()) == 75 + assert set(archive.namelist()) == set(inventory) | {'publication.json'} + exact = [] + for name, row in inventory.items(): + assert not Path(name).is_absolute() and '..' not in Path(name).parts + data = archive.read(name) + assert len(data) == row['published_bytes'] and digest(data) == row['published_sha256'], name + assert not re.search(rb'[A-Za-z]:[\\/]+(?:Users|a)[\\/]', data, re.I), name + assert b'child_pid' not in data and b'process_inventory' not in data, name + if row['treatment'] == 'byte_exact_owned_synthetic_stream': + assert row['original_bytes'] == len(data) and row['original_sha256'] == digest(data), name + exact.append(name) + assert len(exact) == 24 + original = lambda name: inventory[name]['original_sha256'] + identity = publication['hosted_identity'] + assert identity['GITHUB_SHA'] == SOURCE and identity['GITHUB_RUN_ID'] == RUN + assert identity['GITHUB_RUN_ATTEMPT'] == '1' and identity['GITHUB_EVENT_NAME'] == 'push' + assert identity['GITHUB_REF'] == 'refs/heads/perf/grapheme-cpu-hosted-r54-entry3' + assert identity['GITHUB_REPOSITORY'] == 'JavaZeroo/termforge' + assert identity['RUNNER_ENVIRONMENT'] == 'github-hosted' and identity['RUNNER_OS'] == 'Windows' + hosted = read('hosted.json') + assert hosted['source'] == SOURCE and hosted['hosted_identity'] == identity + assert hosted['status'] == 'accounting_complete' and hosted['performance_gate'] == 'not_evaluated' + for key, name in (('runner_sha256','experiments/measure/r54_cpu_hosted.py'), + ('workflow_sha256','.github/workflows/r54-cpu.yml')): + assert digest(subprocess.check_output(['git','show',SOURCE+':'+name],cwd=ROOT)) == hosted[key] + runner = read('diagnostic/runner.json') + assert runner['commit'] == SOURCE and runner['worktree_status'] == '' + for name, sha in runner['sources'].items(): + assert digest(subprocess.check_output(['git','show',SOURCE+':'+name],cwd=ROOT)) == sha, name + preparation = read('bundle/preparation.json') + assert preparation['status'] == 'built_and_audited' and preparation['source'] == SOURCE + plan, builds = read('bundle/plan-v1.json'), read('bundle/build-plan.json') + assert plan['protocol'] == protocol() + assert plan['protocol_sha256'] == digest(canonical(protocol())) + assert builds['harness_source'] == SOURCE and builds['frozen_plan']['sha256'] == original('bundle/plan-v1.json') + assert builds['protocol_sha256'] == plan['protocol_sha256'] + checkout_forms = {name: source_file_matches(SOURCE, name, sha) for name, sha in plan['harness_sha256'].items()} + binding, audit = read('diagnostic/binding.json'), read('bundle/build-audit.json') + assert audit['passed'] is True and audit['formal_samples'] == 0 + assert binding['plan_sha256'] == original('bundle/plan-v1.json') + assert binding['harness_source'] == SOURCE and binding['protocol_sha256'] == plan['protocol_sha256'] + for name, sha in binding['evidence'].items(): + assert original('bundle/'+name) == sha, name + for variant, source in SOURCES.items(): + spec, observed = builds['builds'][variant], audit['builds'][variant] + receipt = read('bundle/'+variant+'-build.json') + assert spec['commit'] == observed['source'] == source['commit'] + assert spec['version'] == observed['version'] == source['version'] + assert len(spec['before_files']) == len(spec['after_files']) == (80 if variant == 'baseline' else 84) + assert set(spec['before_files']) == set(spec['after_files']) + assert set(spec['changed_paths']) == set(spec['overlay_files']) == set(OVERLAYS) + for name, before in spec['before_files'].items(): + checkout_forms[variant+'/'+name] = source_file_matches(source['commit'], name, before) + expected = plan['harness_sha256'][name] if name in OVERLAYS else before + assert spec['after_files'][name] == expected, name + assert spec['overlay_patch_sha256'] == original('bundle/'+variant+'-harness.patch') + assert receipt['status'] == 'built_identity_only' and receipt['exit_code'] == 0 and not receipt['timed_out'] + assert receipt['build_plan_sha256'] == original('bundle/build-plan.json') + assert observed['receipt_sha256'] == original('bundle/'+variant+'-build.json') + assert receipt['binary'] == observed['binary'] == binding['builds'][variant]['binary'] + assert receipt['identity'] == observed['identity'] == read('bundle/'+variant+'-identity.stdout') == IDENTITY + assert read('diagnostic/'+variant+'-identity/stdout.bin') == IDENTITY + assert read('diagnostic/'+variant+'-identity/record.json')['exit_code'] == 0 + assert receipt['command'] == spec['command'] + for flag in ('--release','--locked','--offline','--no-default-features','--jobs','1','x86_64-pc-windows-msvc'): + assert flag in spec['command'] + for name, log in receipt['logs'].items(): + assert log['sha256'] == original('bundle/'+name) + assert log['bytes'] == inventory['bundle/'+name]['original_bytes'] + assert audit['builds']['baseline']['linker'] == audit['builds']['candidate']['linker'] + assert len(audit['short_sanity_calls']) == 4 + for call in audit['short_sanity_calls']: + assert call['passed'] and call['exit_code'] == 0 and not call['timed_out'] + prefix = 'bundle/sanity-'+call['variant']+'-'+call['route']+'/' + for name, stream in call['streams'].items(): + data = archive.read(prefix+name) + assert digest(data) == stream['sha256'] and len(data) == stream['bytes'] + validate_result(read(prefix+'stdout.bin'), .1, expected_sample(dict(variant=call['variant'], route=call['route'], chunk=4096))) + manifest = read('diagnostic/manifest.json') + observations = read('diagnostic/observations/manifest.json') + assert hosted['diagnostic'] == manifest + assert manifest['status'] == observations['status'] == 'accounting_complete' + assert manifest['observations'] == observations + assert observations['attempted'] == observations['accounted_observations'] == 6 + assert observations['performance_gate'] == 'not_evaluated' + for key, name in (('context_sha256','context.json'), ('protocol_sha256','protocol.json')): + assert observations[key] == original('diagnostic/observations/'+name) + rows = [] + for index, variant in enumerate(ORDER): + prefix = f'diagnostic/observations/{index:03d}/' + row = read(prefix+'record.json') + assert row['item'] == dict(sequence=index, variant=variant, route='interactive', chunk=1) + assert row['status'] == 'accounted' and row['exit_code'] == 0 and not row['timed_out'] + for name, stream in row['streams'].items(): + data = archive.read(prefix+name) + assert digest(data) == stream['sha256'] and len(data) == stream['bytes'] + raw = read(prefix+'stdout.bin') + assert raw == row['result'] + validate_sample(raw,row['item']) + env, child, accounting = row['environment'], row['child_cpu'], row['accounting'] + before, after = env['before'], env['after'] + busy = after['user']+after['system']-before['user']-before['system'] + total = busy+after['idle']-before['idle'] + assert abs(busy-env['busy_seconds']) < 1e-8 and abs(total-env['total_seconds']) < 1e-8 + assert child['total_seconds'] == child['kernel_seconds']+child['user_seconds'] + assert child['exit_filetime'] > child['creation_filetime'] + inner, runner_cpu = raw['interval_cpu_seconds'], row['runner_cpu_after_seconds']-row['runner_cpu_before_seconds'] + assert runner_cpu == row['runner_cpu_seconds'] == accounting['runner_cpu_seconds'] + assert child['total_seconds'] >= inner + assert accounting['child_outside_inner_seconds'] == child['total_seconds']-inner + assert accounting['inner_only_balance_seconds'] == busy-inner + assert abs(accounting['inner_only_balance_percent']-(busy-inner)*100/total) < 1e-8 + assert accounting['unassigned_balance_seconds'] == busy-child['total_seconds']-runner_cpu + assert abs(accounting['unassigned_balance_percent']-accounting['unassigned_balance_seconds']*100/total) < 1e-8 + rows.append(dict(item=row['item'],inner_cpu_seconds=inner,child_cpu=child,runner_cpu_seconds=runner_cpu, + environment=env,accounting=accounting,state_fnv1a64=raw['state_fnv1a64'])) + result = dict(schema='termforge-r54-cpu-hosted-validation-v1',source=SOURCE,hosted_identity=identity, + host=read('diagnostic/environment.json'),builds=audit['builds'],observations=rows, + archive=dict(bytes=path.stat().st_size,sha256=digest(path.read_bytes()),files=75,inventory_files=74,byte_exact_streams=24), + source_checkout_forms=checkout_forms,sanity_oracles=4,observation_oracles=6,formal_cost_samples=0, + performance_gate='not_evaluated',old_hosted_event_cause='unknown') + retained = json.loads(path.with_name('validation.json').read_text(encoding='utf-8')) + for name, value in result.items(): + assert retained[name] == value, name + provider = retained['provider'] + assert provider['run'] == int(RUN) and provider['attempt'] == 1 and provider['source'] == SOURCE + assert provider['job'] == 122217071656 and provider['conclusion'] == 'success' + assert provider['artifact_digest'] == 'sha256:'+digest(path.read_bytes()) + assert sum(value == 'git_blob_lf_to_crlf' for value in checkout_forms.values()) == 115 + assert len(checkout_forms) == 168 + print(json.dumps(dict(files=75,hashed_evidence=74,byte_exact_streams=24,sanity_oracles=4,observation_oracles=6, + formal_cost_samples=0,performance_gate='not_evaluated'))) + +if __name__ == '__main__': + main(Path(__file__).with_name('evidence.zip')) diff --git a/experiments/measure/results/2026-10-27-r54-hosted/README.md b/experiments/measure/results/2026-10-27-r54-hosted/README.md new file mode 100644 index 00000000..429335cf --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-hosted/README.md @@ -0,0 +1,24 @@ +# #54 首次 hosted 固定批次:环境护栏停止 + +固定源码 `f734d29bc4f8a120eac59da3acac3169d49231b2` 在 [GitHub run 40490861301](https://github.com/JavaZeroo/termforge/actions/runs/40490861301) 的首次 `windows-2025` 作业中完成两份构建、来源审计及4次短正确性检查。原协议、两个种子、各112位置、2秒CPU/墙钟下限、5%环境诊断护栏均未改变。 + +第一批的十个空闲区间为1.154%–3.125%,通过预检。随后执行24个位置:23个记录有效,第24个(sequence 23,candidate/interactive/chunk 1)输出正确且退出0,但未归属CPU活动上界估计为5.410447761%,超过5%护栏,故停止。原始数据全部保留,没有续跑、补样、删除慢样本或启动第二批。 + +该位置全机总CPU区间为8.375秒,busy为2.484375秒,内层测量进程CPU为2.03125秒,余额0.453125秒。余额包含内层区间以外的准备、oracle、启动和runner工作,不能直接称为其他进程的后台CPU,更不能据此定位具体进程。临时VM的逐进程身份未导出,不能追溯补造归因。 + +**完整批次数为0,不生成配对成本摘要,不从这个前缀计算或验收吞吐回归。** `performance_gate=not_evaluated`;5%不是用户批准的产品性能容差。物理宿主独占、邻居VM、频率稳定、输入/传输/呈现及WindTerm等价仍未测。 + +- [validation.json](validation.json):源码/树、构建/二进制、宿主、停止位置、限制及同源码回归CI。 +- [evidence.zip](evidence.zip):审阅后的164,608B公开副本,SHA-256 `9ade502f03419f55295b4e9f4df9dc136c5c4d50ace02d41417fea1de6952917`。内含131个文件:130份白名单证据和`publication.json`;56份合成stdout/stderr原字节保留。全部24次输出的正确性和完整字段已独立复查。 +- 提供者原归档165,170B、SHA-256 `6a2713dd96d86ce66e2462a142d46b72e915ccc4cfd45bd3b42c5e7486c3cef5`保存在local。产物复审发现两份构建日志各有10处未处理的8.3短profile前缀;未发现凭据。公开副本仅补做这些前缀的别名替换,并更新清单,保留转换前/后哈希和`post_publication_review`;成本数据、顺序和停止状态未改,不冒充原ZIP字节。 +- 复查副本的来源、固定白名单、长/短路径及秘密字段后,未发现逐进程名称/PID、凭据或未处理的workspace/profile前缀。旧作业确实使用旧导出器;后续实现修正不回填旧运行身份,也不触发补跑成本批次。 + +测量源码对应的默认PR/push回归均通过(Windows46/46、Linux29/29);候选两条CI也通过(measurement Windows40/40,Linux39通过/1个Windows专属跳过)。当时相关本地measurement31/31;后续短路径修正另有失败反例、实际Windows别名查询及32/32回归。普通CI与成本作业分开,不用CI成功抵消成本批次失败。日志哈希指向本地保留的UTF-8/LF解码文本,并非提供者日志ZIP原件。 + +归档复查命令不会执行成本负载或修改数据: + +```powershell +python experiments/measure/results/2026-10-27-r54-hosted/verify.py +``` + +下一项调查由[#54](https://github.com/JavaZeroo/termforge/issues/54)接续:另行预声明有界诊断,观测整个测量子进程及runner的CPU,区分自身区间外工作与外部guest活动;本次失败和v1规则保持。扰动/组件归因、完整受控批次及用户预算确认仍未完成。 diff --git a/experiments/measure/results/2026-10-27-r54-hosted/evidence.zip b/experiments/measure/results/2026-10-27-r54-hosted/evidence.zip new file mode 100644 index 00000000..3f12caf7 Binary files /dev/null and b/experiments/measure/results/2026-10-27-r54-hosted/evidence.zip differ diff --git a/experiments/measure/results/2026-10-27-r54-hosted/validation.json b/experiments/measure/results/2026-10-27-r54-hosted/validation.json new file mode 100644 index 00000000..199ea78f --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-hosted/validation.json @@ -0,0 +1,316 @@ +{ + "schema": "termforge-r54-hosted-validation-v1", + "status": "incomplete_first_batch_stopped_by_environment_guard", + "source": "f734d29bc4f8a120eac59da3acac3169d49231b2", + "tree": "361683a329aa7c24d34fc34158b42ea55f4786f7", + "reviewed_local_source": "a8daaab2202fa5a36cbad216959355583eba78f0", + "base": "a7b9685a20396779dc9dff51018470b7638bfdce", + "pr_merge_source": "38c6703403948f79c7112d7340df4087d0689edc", + "pr_merge_tree_matches_reviewed": true, + "protocol_sha256": "fb86f093a1c526979816574ef65896501eaeb9e9c34e7d9a6f2fdd5b61a8e7f6", + "review": { + "standards_remaining": 0, + "spec_remaining": 0, + "read_only": true + }, + "local_validation": { + "measurement_tests": 31, + "passed": 31, + "unittest_seconds": 13.809, + "ctest_seconds": 13.99, + "stdout_sha256": "f31c7251649be1d7d9157eeb3a9d83c8d397e6cadd4a5f80347f689637b6caf5", + "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "hosted": { + "run": 40490861301, + "job": 122144148028, + "attempt": 1, + "conclusion": "failure", + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40490861301", + "runner_labels": [ + "windows-2025" + ], + "contract_tests_passed": 22, + "decoded_job_log_sha256": "fbb8fad6fda8eaad64a940cbd0dae55fdec9690c9cde3b2da7df857c4b0ee1ad", + "build_status": "built_and_audited", + "sanity_passed": 4 + }, + "compiler": { + "rustc_vv": "rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: x86_64-pc-windows-msvc\nrelease: 1.92.0\nLLVM version: 21.1.3\n", + "cargo_vv": "cargo 1.92.0 (344c4567c 2025-10-21)\nrelease: 1.92.0\ncommit-hash: 344c4567c634a25837e3c3476aac08af84cf9203\ncommit-date: 2025-10-21\nhost: x86_64-pc-windows-msvc\nlibgit2: 1.9.1 (sys:0.20.2 vendored)\nlibcurl: 8.15.0-DEV (sys:0.4.83+curl-8.15.0 vendored ssl:Schannel)\nos: Windows 10.0.26100 (Windows 11 ServerDatacenter) [64-bit]\n", + "sha256": "5f3b5087b95e250378dd7bd829ae627d51c149468aae1e38c0f2a85c17cd374e" + }, + "builds": { + "baseline": { + "source": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "version": "0.26.1-dev+termforge.3", + "candidate_tree": "38f901ce2407d73ab7d4ae70987c294849bafa92", + "tracked_candidate_files": 80, + "changed_paths": [ + "experiments/candidates/alacritty/Cargo.toml", + "experiments/candidates/alacritty/src/measure.rs" + ], + "binary": { + "bytes": 523264, + "sha256": "ac7c1d4ba996f9fe2c0d911a077bcab557f5b0b7332cde757dfb3f7db2c499a1" + }, + "identity": { + "allocator_wrapper_compiled": false, + "projection_selectable": true, + "schema": "termforge-core-cost-build-v1", + "timing_gates_compiled": true + }, + "linker": { + "path": "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\bin\\HostX64\\x64\\link.exe", + "sha256": "a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d" + }, + "receipt_sha256": "7cabd0c3610017fdb2f296999e2d96083bf5f4ba245aa0b414aab1d1527fa9ea" + }, + "candidate": { + "source": "3d6d44d3b3e9a9a3e41da98e8711e9f19a211fd8", + "version": "0.26.1-dev+termforge.4", + "candidate_tree": "2cce55fcf2d379440110bbbf8612117cce3edf64", + "tracked_candidate_files": 84, + "changed_paths": [ + "experiments/candidates/alacritty/Cargo.toml", + "experiments/candidates/alacritty/src/measure.rs" + ], + "binary": { + "bytes": 580096, + "sha256": "e674f0df59af27151fe2c1ad916b6cb4f4dd6ccd783ed225fe0107e13b74ae0a" + }, + "identity": { + "allocator_wrapper_compiled": false, + "projection_selectable": true, + "schema": "termforge-core-cost-build-v1", + "timing_gates_compiled": true + }, + "linker": { + "path": "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\bin\\HostX64\\x64\\link.exe", + "sha256": "a8c42e25c91ca2453e1110540ac08f0fc12d7539454a11b9af8baeecb469837d" + }, + "receipt_sha256": "eeef0cfd10f469bb44ddade96cae6c81263c600cfa014a984bc4466a452c14f9" + } + }, + "batches": [ + { + "seed": 20261026, + "status": "failed", + "attempted": 24, + "valid_samples": 23, + "error": "ValueError: ValueError: sample 23 failed: ValueError: host activity guard failed; sample retained, batch stopped", + "host": { + "platform": "Windows-2025Server-10.0.26100-SP0", + "python": "3.12.10", + "psutil": "7.2.2", + "cpu_logical": 4, + "cpu_physical": 2, + "cpu": { + "ProcessorNameString": "AMD EPYC 9V45 96-Core Processor ", + "Identifier": "AMD64 Family 26 Model 2 Stepping 1" + }, + "windows": { + "CurrentBuildNumber": "26100", + "UBR": 33650, + "DisplayVersion": "24H2" + } + }, + "environment_status": "low_activity_observed", + "busy_percent": [ + 1.5625, + 1.5625, + 1.953125, + 1.171875, + 1.550387596899225, + 1.550387596899225, + 1.953125, + 3.125, + 1.171875, + 1.1538461538461537 + ] + } + ], + "all_attempted_output_oracles_passed": 24, + "all_attempted_processes_exit_zero": 24, + "stopped_sample": { + "item": { + "sequence": 23, + "repeat": 1, + "route": "interactive", + "chunk": 1, + "variant": "candidate", + "observe": 0 + }, + "status": "failed", + "error": "ValueError: host activity guard failed; sample retained, batch stopped", + "guest_total_cpu_seconds": 8.375, + "guest_busy_cpu_seconds": 2.484375, + "inner_measured_process_cpu_seconds": 2.03125, + "unattributed_cpu_seconds": 0.453125, + "unattributed_busy_percent_upper_estimate": 5.41044776119403 + }, + "second_seed_started": false, + "full_batches_completed": 0, + "paired_cost_summary_produced": false, + "performance_gate": "not_evaluated", + "numerical_product_budget": null, + "archive": { + "path": "evidence.zip", + "bytes": 164608, + "sha256": "9ade502f03419f55295b4e9f4df9dc136c5c4d50ace02d41417fea1de6952917", + "provider_artifact": 12728432619, + "files": 131, + "inventory_verified_files": 130, + "byte_exact_synthetic_streams": 56, + "individual_publication_sha256": "75f98a70ae4567eebb7686c5284a84df5f4b137220fe0032cbd6579aa82c36dd", + "privacy_review": "Reviewed public copy additionally replaces the observed Windows 8.3 profile aliases in two build logs. No process identities, credentials or remaining workspace/profile prefixes found. Measurement streams unchanged.", + "provider_archive_bytes": 165170, + "provider_archive_sha256": "6a2713dd96d86ce66e2462a142d46b72e915ccc4cfd45bd3b42c5e7486c3cef5", + "provider_original_retained_locally": true, + "post_publication_review": [ + { + "path": "bundle/baseline-build.stderr", + "previous_published_sha256": "812ba8957f4fcf9b5019361b2b022ad529b695dbc89f89b7bec7c95b89969626", + "reviewed_sha256": "585a85a2909b2e5325e6573ee5c9cf6e03a1a389adf09409b7f9da92dd87bee1", + "replacements": 10 + }, + { + "path": "bundle/candidate-build.stderr", + "previous_published_sha256": "44c59810fe10f93df597115060e6cf86749350a96f92fe9df174d0241da66579", + "reviewed_sha256": "b25d91f15ca587c2fa37420f3bc2a5ca357426a73eb5cc2885b522f87fb66860", + "replacements": 10 + } + ] + }, + "ci": [ + { + "run": 40490884320, + "event": "pull_request", + "name": "default", + "jobs": [ + { + "job": 122144220963, + "platform": "windows-2025", + "passed": 46, + "collected": 46, + "seconds": 276.62, + "decoded_log_sha256": "46d7dc7a0d2adcd51de07e6aa27c132a155f3e313a4bf947bd220df426d98002" + }, + { + "job": 122144221116, + "platform": "ubuntu-24.04", + "passed": 29, + "collected": 29, + "seconds": 16.67, + "decoded_log_sha256": "1df6af066a76fa8d090a46f38a1612d7d1d4c6e3b3db9a5b9a858d19fccb752d" + } + ], + "conclusion": "success", + "attempt": 1, + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40490884320" + }, + { + "run": 40490861189, + "event": "push", + "name": "default", + "jobs": [ + { + "job": 122144147319, + "platform": "windows-2025", + "passed": 46, + "collected": 46, + "seconds": 282.38, + "decoded_log_sha256": "318d044f96dcccb27983100d3fdbd897073142e63e656e369f6d838d45991d16" + }, + { + "job": 122144147638, + "platform": "ubuntu-24.04", + "passed": 29, + "collected": 29, + "seconds": 16.02, + "decoded_log_sha256": "14366db79e80b31887b26d5e97d0bc9bdd7de001f83812400afd00c63c09f51b" + } + ], + "conclusion": "success", + "attempt": 1, + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40490861189" + }, + { + "run": 40490883616, + "event": "pull_request", + "name": "candidate", + "measurement_jobs": [ + { + "job": 122144218593, + "platform": "windows-2025", + "passed": 40, + "collected": 40, + "skipped": 0, + "seconds": 30.304, + "decoded_log_sha256": "713d9b818aa14dd38480205e81c9d90eee74e94b717ca81ecdbb1b67dade0df9" + }, + { + "job": 122144218760, + "platform": "ubuntu-24.04", + "passed": 39, + "collected": 40, + "skipped": 1, + "seconds": 22.982, + "decoded_log_sha256": "b3e3033bd7c232de80b702c7945c1292dbe1ef1e9630416c8021318521fae7e0" + } + ], + "conclusion": "success", + "attempt": 1, + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40490883616" + }, + { + "run": 40490861431, + "event": "push", + "name": "candidate", + "measurement_jobs": [ + { + "job": 122144148011, + "platform": "windows-2025", + "passed": 40, + "collected": 40, + "skipped": 0, + "seconds": 29.831, + "decoded_log_sha256": "f2ecb43c16ef0a8c3f6d75b6d1989f16b9027af852def85c03cb94dbd8f39f00" + }, + { + "job": 122144147926, + "platform": "ubuntu-24.04", + "passed": 39, + "collected": 40, + "skipped": 1, + "seconds": 25.717, + "decoded_log_sha256": "8015b82b14881aff82fa1d71db2a1e801d83aa604ac8a83a5a49d281c36f5f2f" + } + ], + "conclusion": "success", + "attempt": 1, + "url": "https://github.com/JavaZeroo/termforge/actions/runs/40490861431" + } + ], + "ci_logs_representation": "decoded UTF-8, LF; hashes identify retained local text, not provider ZIP bytes", + "limitations": [ + "The batch is incomplete: do not calculate or accept a throughput regression from this prefix.", + "5 percent is the frozen guest CPU diagnostic guard, not a user-approved product performance tolerance.", + "The remainder includes preparation/oracle/startup and runner CPU excluded by the inner timing interval; it does not identify the responsible process.", + "Per-process identities were not exported from the temporary VM; they cannot support retrospective attribution.", + "Physical host exclusivity, neighboring VM load, frequency stability, GUI/input/receive/presentation and WindTerm equivalence remain unmeasured.", + "No workflow rerun, skipped sample, replacement sample, relaxed guard or resumed second seed." + ], + "next_investigation": "Separately predeclare a bounded diagnostic of whole-child lifetime CPU and runner CPU to distinguish own work from external guest activity; preserve this first failure and the v1 acceptance rules.", + "post_run_publication_fix": { + "reason": "Windows compiler logs used short profile aliases absent from the original prefix list.", + "native_windows_short_path_lookup": "passed_same_directory", + "path_variant_regression": "red_then_green", + "measurement_tests": 32, + "passed": 32, + "unittest_seconds": 15.152, + "stdout_sha256": "c8c96d18f0a4f88593607294a831a3163735f74fa9e650a1c74424008b61e95c", + "cost_execution": "not_repeated; branch update no longer launches cost job", + "original_cost_source_unchanged": true + } +} diff --git a/experiments/measure/results/2026-10-27-r54-hosted/verify.py b/experiments/measure/results/2026-10-27-r54-hosted/verify.py new file mode 100644 index 00000000..ebe62162 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-hosted/verify.py @@ -0,0 +1,78 @@ +"""Verify the immutable first hosted failure archive; do not run measurements.""" +import hashlib +import io +import json +from pathlib import Path +import sys +import zipfile + +sys.path.insert(0, str(Path(__file__).resolve().parents[4])) +from experiments.measure.r54_contract import validate_sample + + +def main(): + data = Path(__file__).with_name('evidence.zip').read_bytes() + assert len(data) == 164608 + assert hashlib.sha256(data).hexdigest() == '9ade502f03419f55295b4e9f4df9dc136c5c4d50ace02d41417fea1de6952917' + with zipfile.ZipFile(io.BytesIO(data)) as archive: + read = lambda name: json.loads(archive.read(name).decode('utf-8')) + publication = read('publication.json') + review = publication['post_publication_review'] + assert review['source_provider_archive_sha256'] == '6a2713dd96d86ce66e2462a142d46b72e915ccc4cfd45bd3b42c5e7486c3cef5' + assert review['measurement_streams_unchanged'] is True + assert {item['path'] for item in review['changes']} == {'bundle/baseline-build.stderr', 'bundle/candidate-build.stderr'} + inventory = {item['path']: item for item in publication['artifacts']} + assert len(inventory) == len(publication['artifacts']) == 130 + assert len(archive.namelist()) == 131 + assert set(archive.namelist()) == set(inventory) | {'publication.json'} + exact = 0 + for name, item in inventory.items(): + payload = archive.read(name) + assert len(payload) == item['published_bytes'], name + assert hashlib.sha256(payload).hexdigest() == item['published_sha256'], name + if item['treatment'] == 'byte_exact_owned_synthetic_stream': + assert item['original_bytes'] == len(payload), name + assert item['original_sha256'] == item['published_sha256'], name + exact += 1 + assert exact == 56 + hosted = read('hosted.json') + assert hosted['status'] == 'failed' and hosted['performance_gate'] == 'not_evaluated' + assert hosted['source'] == 'f734d29bc4f8a120eac59da3acac3169d49231b2' + assert hosted['hosted_identity'] == publication['hosted_identity'] + assert hosted['hosted_identity']['GITHUB_RUN_ID'] == '40490861301' + assert hosted['hosted_identity']['GITHUB_RUN_ATTEMPT'] == '1' + prefix = 'batch-20261026/' + manifest = read(prefix + 'manifest.json') + assert manifest['status'] == 'failed' and manifest['attempted'] == 24 and manifest['valid_samples'] == 23 + plan = read('bundle/plan-v1.json') + schedule = plan['protocol']['schedules'][0] + assert schedule['seed'] == 20261026 + records = [] + for sequence in range(24): + directory = prefix + f'samples/{sequence:03d}/' + record = read(directory + 'record.json') + assert record['item'] == schedule['samples'][sequence] + assert record['status'] == ('valid' if sequence < 23 else 'failed') + assert record['exit_code'] == 0 and not record['timed_out'] + result = read(directory + 'stdout.bin') + assert result == record['result'] + validate_sample(result, record['item']) + for name, identity in record['streams'].items(): + payload = archive.read(directory + name) + assert len(payload) == identity['bytes'] and hashlib.sha256(payload).hexdigest() == identity['sha256'] + environment = record['environment'] + remainder = environment['busy_seconds'] - result['interval_cpu_seconds'] + upper = remainder * 100 / environment['total_seconds'] + assert abs(upper - environment['unattributed_busy_percent_upper_estimate']) < 1e-12 + assert (0 <= upper <= 5) if sequence < 23 else upper > 5 + records.append(record) + assert [json.loads(line) for line in archive.read(prefix + 'samples/samples.jsonl').splitlines()] == records + assert prefix + 'samples/summary.json' not in inventory + assert not any(name.startswith('batch-20261027/') for name in inventory) + print(json.dumps(dict(archive_files=131, hashed_evidence_files=130, byte_exact_streams=56, + attempted=24, valid_records=23, correct_outputs=24, + full_batches=0, performance_gate='not_evaluated'))) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/results/2026-10-27-r54-runner/validation.json b/experiments/measure/results/2026-10-27-r54-runner/validation.json new file mode 100644 index 00000000..869bd4a8 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-runner/validation.json @@ -0,0 +1,197 @@ +{ + "schema": "termforge-r54-runner-validation-v1", + "scope": "frozen batch executor, build binding, raw artifacts, paired statistics and environment guard", + "performance_gate": "not_evaluated", + "budget_status": "not_set", + "formal_cost_samples": 0, + "protocol_sha256": "fb86f093a1c526979816574ef65896501eaeb9e9c34e7d9a6f2fdd5b61a8e7f6", + "build_harness_source": "8a13782aaac03780617e177b56ec55d3cb9c79be", + "builds": { + "baseline": { + "source": "65988f5f20f62294ff52fcac5b5b81e563f93f3b", + "version": "0.26.1-dev+termforge.3", + "tracked_candidate_files": 80, + "binary": { + "bytes": 523264, + "sha256": "33cb90beebffc57f07aa993b8ea635eaf461b49d86c143939a41f6242c087b6d" + } + }, + "candidate": { + "source": "3d6d44d3b3e9a9a3e41da98e8711e9f19a211fd8", + "version": "0.26.1-dev+termforge.4", + "tracked_candidate_files": 84, + "binary": { + "bytes": 579584, + "sha256": "d4d068be44a0a9ec733ad17f0323765ee38ffbf775e6efacb4632c1dce6b3f01" + } + } + }, + "actual_preflight": { + "runner_source": "fb5557c5519d9e0684ddca8332c6c57d6914a91f", + "source_attestation": "orchestrator recorded clean commit before invocation; this initial runner omitted runner.json on rejected preflight", + "status": "failed", + "environment_status": "rejected_busy_host", + "started_utc": "2026-10-27T09:34:09.959438+00:00", + "finished_utc": "2026-10-27T09:34:21.403306+00:00", + "attempted_cost_samples": 0, + "valid_cost_samples": 0, + "policy": { + "schema": "termforge-r54-environment-guard-v1", + "idle_intervals": 10, + "idle_interval_seconds": 1, + "max_busy_percent": 5.0, + "max_unattributed_busy_percent": 5.0, + "product_performance_budget": false, + "measurement": "GetSystemTimes; user + kernel - idle; all logical CPUs, at most 64", + "scope": "guest-visible CPU activity only; physical host contention and frequency stability remain unknown" + }, + "windows": { + "CurrentBuildNumber": "26200", + "UBR": 9711, + "DisplayVersion": "25H2" + }, + "cpu": { + "ProcessorNameString": "Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz", + "Identifier": "Intel64 Family 6 Model 158 Stepping 13" + }, + "cpu_logical": 8, + "cpu_physical": 8, + "busy_percent_each_interval": [ + 11.9140625, + 18.26923076923077, + 11.153846153846153, + 21.27659574468085, + 25.436893203883496, + 32.88461538461539, + 31.73076923076923, + 17.348927875243664, + 16.184971098265898, + 17.692307692307693 + ], + "known_context": "shared workstation; personal desktop apps and read-only review remained; no known concurrent builds or tests", + "raw_evidence": { + "manifest.json": { + "bytes": 360, + "sha256": "fddc5f6daa6a387d329b8634e9aa7115c923cbbc83b7c212b2eaafa9a0704c6f" + }, + "environment.json": { + "bytes": 240917, + "sha256": "2c789e8ce31972d82ee6f2fe34466c8bc405625b00298fde51a479f4216b1c4b" + }, + "environment-policy.json": { + "bytes": 421, + "sha256": "2a002340e31ad68b80195d3c1d5b0d4902c89fb1a4795d58cf080d50f9091f4d" + }, + "inputs/binding.json": { + "bytes": 4266, + "sha256": "9bad6e1b46d49cb707a05366fb2c72cb96c06f5117e0c3f0dbadd9b72f348441" + }, + "baseline-identity/stdout.bin": { + "bytes": 135, + "sha256": "633be0c64cddc9990f89fc1c635d320a1d7561202cc16fd0b59187ed5713a611" + }, + "candidate-identity/stdout.bin": { + "bytes": 135, + "sha256": "633be0c64cddc9990f89fc1c635d320a1d7561202cc16fd0b59187ed5713a611" + } + }, + "process_identities_published": false, + "private_paths_published": false, + "repeated_after_rejection": false + }, + "related_regression": { + "source": "f72d13605b11493a8d9a8d8d438145c170b47d8e", + "cmake_configuration": "Windows x64, Release, default core, core_measure target only", + "native_build_source": "fb5557c5519d9e0684ddca8332c6c57d6914a91f", + "ctest_filter": "^measure$", + "ctest_exit_code": 0, + "python_tests": 25, + "python_elapsed_seconds": 12.868, + "native_measure_source_unchanged_since_build": true, + "logs": { + "0.stdout": { + "bytes": 786, + "sha256": "4142bee10ec21c168c53689bdc641f143d5977945aed8ada1f74e3cc8b224185" + }, + "0.stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "1.stdout": { + "bytes": 294, + "sha256": "53171f7fc96805b2101a008dc99b9ce8d949b66fca649e6798f845ad0275ff36" + }, + "1.stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "ctest-final.stdout": { + "bytes": 6138, + "sha256": "1c3df9c5aa4f662ccf738f136ea9a2e52ad3f9a27459b0dfbc97dcc494310d19" + }, + "ctest-final.stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + "synthetic_process_fixture_calls_in_order_test": 112, + "synthetic_fixture_times_are_measurements": false, + "full_application_regression": "not run locally; delegated to normal CI after publication" + }, + "review_repairs": { + "source": "f72d13605b11493a8d9a8d8d438145c170b47d8e", + "changes": [ + "record runner identity before preparation can fail", + "strict UTF-8 decoding before JSON while retaining original bytes", + "unique atomic-write temporary names preserve interruption recording" + ], + "each_counterexample_observed_failing_then_passing": true, + "original_preflight_artifacts_rewritten": false + }, + "remaining": [ + "two original 112-sample cost batches on a controlled host", + "allocator and projection perturbation diagnosis", + "component cost attribution", + "user-confirmed numerical budget", + "any optimization and full correctness regression" + ], + "endpoints": { + "receive_seconds": null, + "input_latency_seconds": null, + "presented_done": null + }, + "ci_dependency_repair": { + "source": "a86e7fc186b87720a46c689d7de29032d50c809c", + "initial_published_source": "fa47edadbc259057a841db32181d1d73095f4a10", + "initial_linux_candidate_jobs": [ + 122118298338, + 122118279790 + ], + "failure": "ModuleNotFoundError: No module named psutil while importing pure environment statistics", + "reproduced_with_python_no_site_packages": true, + "repair": "Import psutil only inside real host-sampling functions; pure statistics retain standard-library-only imports", + "python_tests": 26, + "python_elapsed_seconds": 12.949, + "ctest_exit_code": 0, + "formal_cost_samples": 0, + "logs": { + "ci-122118298338.log": { + "bytes": 102800, + "sha256": "9babc825cff5781142f42e741137654cbcb247518bad8bc0a6a14f03b4c93e89" + }, + "ci-122118279790.log": { + "bytes": 102205, + "sha256": "43e81e8186a9a0930a8bab823dadded525441be37fdd0b12cca3241240ed7c42" + }, + "ctest-dependency-fix.stdout": { + "bytes": 6327, + "sha256": "3b939917d3011e5618aff624fbe0bca8298099471a6b98d39bc761988944da11" + }, + "ctest-dependency-fix.stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + "initial_preflight_repeated": false + } +} diff --git a/experiments/measure/test_r54_batch.py b/experiments/measure/test_r54_batch.py new file mode 100644 index 00000000..d5918f7a --- /dev/null +++ b/experiments/measure/test_r54_batch.py @@ -0,0 +1,211 @@ +"""Synthetic process/artifact contracts; these tests do not measure core speed.""" +import copy +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + +from experiments.measure.r54_contract import SOURCES, sample_order +from experiments.measure.test_r54_contract import sample + + +def records(seed): + baseline = (2, 4, 6, 8, 10, 12, 20) + candidate = (4, 20, 12, 40, 20, 48, 40) + result = [] + for item in sample_order(seed): + value = sample() + value.update(route=item['route'], chunk=item['chunk'], + bytes_per_round=78000 if item['route'] == 'project' else 22000, + state_fnv1a64=SOURCES[item['variant']]['states'][item['route']]) + cpu = (baseline if item['variant'] == 'baseline' else candidate)[item['repeat']] + value.update(interval_cpu_seconds=cpu, interval_wall_seconds=cpu + 1) + result.append(dict(item=item, result=value, status='valid')) + return result + + +class SummaryTests(unittest.TestCase): + def test_pairs_are_not_ratio_of_independent_medians(self): + from experiments.measure.r54_batch import summarize + value = summarize(records(20261026), 20261026) + self.assertEqual(len(value['groups']), 8) + self.assertEqual(value['performance_gate'], 'not_evaluated') + for group in value['groups']: + self.assertEqual(group['paired_cpu_ratios'], [2, 5, 2, 5, 2, 4, 2]) + self.assertEqual(group['paired_cpu_ratio_median'], 2) + cpu = group['samples'] + self.assertAlmostEqual(cpu['candidate']['cpu_ns_per_byte']['median'] / + cpu['baseline']['cpu_ns_per_byte']['median'], 2.5) + self.assertEqual(cpu['baseline']['cpu_ns_per_byte']['count'], 7) + + def test_missing_reordered_failed_or_wrong_identity_never_gets_summary(self): + from experiments.measure.r54_batch import summarize + valid = records(20261027) + broken = [] + broken.append(valid[:-1]) + broken.append([valid[1], valid[0], *valid[2:]]) + for field, value in (('status', 'failed'), ('item', valid[1]['item'])): + changed = copy.deepcopy(valid) + changed[0][field] = value + broken.append(changed) + wrong_state = copy.deepcopy(valid) + wrong_state[0]['result']['state_fnv1a64'] = 'bad' + broken.append(wrong_state) + for values in broken: + with self.assertRaises(ValueError): + summarize(values, 20261027) + + +class ProcessArtifactsTests(unittest.TestCase): + def test_interrupted_temporary_file_does_not_block_final_manifest(self): + from experiments.measure.r54_batch import write_json + with tempfile.TemporaryDirectory() as folder: + path = Path(folder) / 'manifest.json' + path.write_text('{"status":"running","active_sequence":3}', encoding='utf-8') + stale = Path(folder) / 'manifest.json.tmp' + stale.write_bytes(b'{"status":') + write_json(path, dict(status='interrupted', active_sequence=3)) + self.assertEqual(json.loads(path.read_text()), dict(status='interrupted', active_sequence=3)) + self.assertEqual(stale.read_bytes(), b'{"status":') + + def test_non_utf8_json_is_preserved_and_rejected_before_second_sample(self): + from experiments.measure.r54_batch import run_schedule + payload = json.dumps(records(20261026)[0]['result']).encode('utf-16') + with tempfile.TemporaryDirectory() as folder: + command = [sys.executable, '-c', 'import os,sys; os.write(1,bytes.fromhex(sys.argv[1]))', payload.hex()] + output = Path(folder) / 'batch' + manifest = run_schedule({variant: command for variant in SOURCES}, 20261026, output, + {'evidence_kind': 'synthetic_process_fixture'}) + self.assertEqual(manifest['status'], 'failed') + self.assertEqual(manifest['attempted'], 1) + self.assertEqual(manifest['valid_samples'], 0) + self.assertEqual((output / '000/stdout.bin').read_bytes(), payload) + + def test_timeout_keeps_exact_stream_bytes(self): + from experiments.measure.r54_batch import capture + with tempfile.TemporaryDirectory() as folder: + output = Path(folder) + code = "import os,time; os.write(1,b'partial\\xff'); os.write(2,b'error\\xfe'); time.sleep(10)" + value = capture([sys.executable, '-c', code], output, timeout=1) + self.assertTrue(value['timed_out']) + self.assertEqual((output / 'stdout.bin').read_bytes(), b'partial\xff') + self.assertEqual((output / 'stderr.bin').read_bytes(), b'error\xfe') + + def test_failure_preserves_attempt_and_stops_without_overwriting(self): + from experiments.measure.r54_batch import run_schedule + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + fixture = root / 'fixture.py' + fixture.write_text("import os; os.write(1,b'invalid\\xff'); os.write(2,b'diagnostic\\xfe')", encoding='utf-8') + commands = {variant: [sys.executable, str(fixture)] for variant in SOURCES} + output = root / 'batch' + manifest = run_schedule(commands, 20261026, output, {'evidence_kind': 'synthetic_process_fixture'}) + self.assertEqual(manifest['status'], 'failed') + self.assertEqual(manifest['attempted'], 1) + self.assertEqual(manifest['valid_samples'], 0) + self.assertFalse((output / 'summary.json').exists()) + self.assertEqual((output / '000/stdout.bin').read_bytes(), b'invalid\xff') + self.assertEqual((output / '000/stderr.bin').read_bytes(), b'diagnostic\xfe') + original = (output / 'manifest.json').read_bytes() + with self.assertRaises(FileExistsError): + run_schedule(commands, 20261026, output, {}) + self.assertEqual((output / 'manifest.json').read_bytes(), original) + + def test_complete_real_subprocess_sequence_preserves_all_pairs(self): + from experiments.measure.r54_batch import run_schedule + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + # Independent literal fake results exercise process ordering and artifact + # handling. They are labelled synthetic and are never timing evidence. + (root / 'values.json').write_text(json.dumps(records(20261027)), encoding='utf-8') + fixture = root / 'fixture.py' + fixture.write_text('''import json,pathlib,sys +p=pathlib.Path(__file__).parent +count=p/'count' +i=int(count.read_text()) if count.exists() else 0 +record=json.loads((p/'values.json').read_text())[i] +item=record['item'] +assert sys.argv[1:]==[item['variant'],item['route'],str(item['chunk']),'0','2.0'] +count.write_text(str(i+1)) +print(json.dumps(record['result'])) +''', encoding='utf-8') + commands = {variant: [sys.executable, str(fixture), variant] for variant in SOURCES} + output = root / 'batch' + manifest = run_schedule(commands, 20261027, output, {'evidence_kind': 'synthetic_process_fixture'}) + self.assertEqual(manifest['status'], 'samples_complete') + self.assertEqual(manifest['valid_samples'], 112) + self.assertEqual(int((root / 'count').read_text()), 112) + rows = [json.loads(line) for line in (output / 'samples.jsonl').read_text().splitlines()] + self.assertEqual([row['item'] for row in rows], sample_order(20261027)) + self.assertEqual(len(list(output.glob('*/stdout.bin'))), 112) + self.assertEqual(json.loads((output / 'summary.json').read_text())['groups'][0]['paired_cpu_ratio_median'], 2) + + +class EnvironmentTests(unittest.TestCase): + def test_pure_environment_statistics_import_without_site_packages(self): + command = [sys.executable, '-S', '-c', + 'from experiments.measure.r54_environment import cpu_delta, qualify_idle; print("imported")'] + result = subprocess.run(command, cwd=Path(__file__).resolve().parents[2], + capture_output=True, timeout=10) + self.assertEqual(result.returncode, 0, result.stderr.decode('utf-8', errors='replace')) + self.assertEqual(result.stdout.strip(), b'imported') + + def test_environment_qualification_does_not_hide_one_busy_interval(self): + from experiments.measure.r54_environment import qualify_idle, cpu_delta + # Windows user + system + idle count all logical CPUs. interrupt/dpc + # are already included in system and must not be added again. + before = dict(user=10, system=20, idle=70, interrupt=2, dpc=3) + after = dict(user=11, system=21, idle=118, interrupt=2.5, dpc=3.5) + observed = cpu_delta(before, after) + self.assertEqual(observed['busy_seconds'], 2) + self.assertEqual(observed['total_seconds'], 50) + self.assertEqual(observed['busy_percent'], 4) + self.assertTrue(qualify_idle([observed] * 10)) + self.assertFalse(qualify_idle([observed] * 9)) + self.assertFalse(qualify_idle([observed] * 9 + [dict(observed, busy_percent=6)])) + with self.assertRaises(ValueError): + cpu_delta(after, before) + + +class FrozenInputTests(unittest.TestCase): + @unittest.skipUnless(os.name == 'nt', 'the execution entry point binds Windows builds') + def test_failed_preparation_still_identifies_the_runner(self): + from experiments.measure.r54_batch import run + with tempfile.TemporaryDirectory() as folder: + output = Path(folder) / 'failed' + manifest = run(Path(folder) / 'absent-plan', folder, Path(folder) / 'absent-environment', output, 20261026) + self.assertEqual(manifest['status'], 'failed') + self.assertEqual(manifest['attempted'], 0) + identity = json.loads((output / 'runner.json').read_text()) + self.assertEqual(len(identity['commit']), 40) + self.assertIn('experiments/measure/r54_batch.py', identity['sources']) + self.assertIn('worktree_status', identity) + + def test_changed_schedule_harness_or_result_injection_is_rejected(self): + from experiments.measure.r54_contract import prepare + from experiments.measure.r54_inputs import load_plan + with tempfile.TemporaryDirectory() as folder: + path = Path(folder) / 'plan.json' + original = prepare(path) + self.assertEqual(load_plan(path), original) + changes = [] + changed = copy.deepcopy(original) + changed['protocol']['schedules'][0]['samples'].reverse() + changes.append(changed) + changed = copy.deepcopy(original) + changed['harness_sha256'].pop('experiments/measure/run.py') + changes.append(changed) + changed = copy.deepcopy(original) + changed['environment_evidence'] = {'idle': True} + changes.append(changed) + for changed in changes: + path.write_text(json.dumps(changed), encoding='utf-8') + with self.assertRaises(ValueError): + load_plan(path) + + +if __name__ == '__main__': + unittest.main() diff --git a/experiments/measure/test_r54_cpu_diagnostic.py b/experiments/measure/test_r54_cpu_diagnostic.py new file mode 100644 index 00000000..1a3925da --- /dev/null +++ b/experiments/measure/test_r54_cpu_diagnostic.py @@ -0,0 +1,104 @@ +"""Process/artifact boundary tests; synthetic CPU work is not a core benchmark.""" +import json +import os +from pathlib import Path +import sys +import tempfile +import unittest + + +class AccountingTests(unittest.TestCase): + def test_own_interval_excluded_cpu_is_separated_and_negative_balance_is_retained(self): + from experiments.measure.r54_cpu_diagnostic import partition_cpu + record = dict(environment=dict(total_seconds=10, busy_seconds=3), + child_cpu=dict(total_seconds=2.25), runner_cpu_seconds=.125) + value = partition_cpu(record, 2) + self.assertEqual(value['inner_only_balance_seconds'], 1) + self.assertEqual(value['child_outside_inner_seconds'], .25) + self.assertEqual(value['unassigned_balance_seconds'], .625) + self.assertEqual(value['unassigned_balance_percent'], 6.25) + record['environment']['busy_seconds'] = 2.25 + self.assertEqual(partition_cpu(record, 2)['unassigned_balance_seconds'], -.125) + with self.assertRaises(ValueError): + partition_cpu(record, 3) + for invalid in (float('nan'), float('inf'), -1): + record['runner_cpu_seconds'] = invalid + with self.assertRaises(ValueError): + partition_cpu(record, 2) + + +def native_environment_supported(): + if os.name != 'nt' or sys.implementation.name != 'cpython' or sys.version_info[:2] != (3, 12): + return False + try: + import psutil + except ImportError: + # The general regression job runs this module without the measurement + # requirements; a missing observer means "not supported here", not a + # collection error, so the decorator still skips instead of failing. + return False + return 0 < (psutil.cpu_count() or 0) <= 64 + + +@unittest.skipUnless(native_environment_supported(), 'native diagnostic requires Windows CPython 3.12, 1..64 CPUs') +class ProcessAccountingTests(unittest.TestCase): + def test_lifetime_cpu_includes_work_outside_child_reported_interval(self): + from experiments.measure.r54_cpu_diagnostic import capture_cpu + code = '''import json,os,time +def burn(seconds): + start=time.process_time() + while time.process_time()-start < seconds: + pass +burn(.15) +start=time.process_time() +burn(.05) +inner=time.process_time()-start +burn(.15) +print(json.dumps({'inner_cpu':inner,'pid':os.getpid()})) +''' + with tempfile.TemporaryDirectory() as folder: + # A Windows venv executable is a redirector which starts another + # process. Use the actual interpreter to test the direct-child seam. + result = capture_cpu([sys._base_executable, '-c', code], Path(folder), timeout=5) + self.assertEqual(result['exit_code'], 0, result) + self.assertNotIn('error', result) + child = json.loads((Path(folder) / 'stdout.bin').read_bytes()) + self.assertEqual(result['child_pid'], child['pid']) + self.assertGreaterEqual(result['child_cpu']['total_seconds'] - child['inner_cpu'], .29) + self.assertGreater(result['child_cpu']['exit_filetime'], result['child_cpu']['creation_filetime']) + self.assertGreater(result['environment']['total_seconds'], 0) + self.assertGreaterEqual(result['runner_cpu_seconds'], 0) + + def test_timeout_retains_partial_streams_and_exited_child_accounting(self): + from experiments.measure.r54_cpu_diagnostic import capture_cpu + code = "import os,time; os.write(1,b'partial\\xff'); os.write(2,b'error\\xfe'); time.sleep(10)" + with tempfile.TemporaryDirectory() as folder: + result = capture_cpu([sys._base_executable, '-c', code], Path(folder), timeout=1) + self.assertTrue(result['timed_out']) + self.assertIsNotNone(result['exit_code']) + self.assertGreater(result['child_cpu']['exit_filetime'], 0) + self.assertEqual((Path(folder) / 'stdout.bin').read_bytes(), b'partial\xff') + self.assertEqual((Path(folder) / 'stderr.bin').read_bytes(), b'error\xfe') + + def test_bad_output_stops_diagnostic_without_overwrite_or_cost_summary(self): + from experiments.measure.r54_cpu_diagnostic import run_observations + command = [sys._base_executable, '-c', "import os; os.write(1,b'bad\\xff')"] + with tempfile.TemporaryDirectory() as folder: + output = Path(folder) / 'observations' + context = {'evidence_kind': 'synthetic_process_fixture'} + result = run_observations({name: command for name in ('baseline', 'candidate')}, output, context) + self.assertEqual(result['status'], 'failed') + self.assertEqual(result['attempted'], 1) + self.assertEqual(result['accounted_observations'], 0) + self.assertEqual((output / '000/stdout.bin').read_bytes(), b'bad\xff') + self.assertFalse((output / '001').exists()) + self.assertFalse((output / 'summary.json').exists()) + self.assertEqual(result['performance_gate'], 'not_evaluated') + original = (output / 'manifest.json').read_bytes() + with self.assertRaises(FileExistsError): + run_observations({name: command for name in ('baseline', 'candidate')}, output, context) + self.assertEqual((output / 'manifest.json').read_bytes(), original) + + +if __name__ == '__main__': + unittest.main() diff --git a/experiments/measure/test_r54_cpu_hosted.py b/experiments/measure/test_r54_cpu_hosted.py new file mode 100644 index 00000000..62738e2b --- /dev/null +++ b/experiments/measure/test_r54_cpu_hosted.py @@ -0,0 +1,106 @@ +"""Synthetic publication and hosted-identity boundaries, never actual hosted runs.""" +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + + +class CpuHostedBoundaryTests(unittest.TestCase): + def test_wrong_experiment_or_stale_run_is_rejected_before_publication_staging(self): + from experiments.measure import r54_publish as publisher + identity = dict(GITHUB_RUN_ID='123', GITHUB_RUN_ATTEMPT='1', GITHUB_SHA='a' * 40) + with tempfile.TemporaryDirectory() as folder: + root = Path(folder).resolve() + raw = root / 'local/raw' + raw.mkdir(parents=True) + output = root / 'local/public' + for schema, recorded in (('termforge-r54-hosted-v1', identity), + ('termforge-r54-cpu-hosted-v1', dict(identity, GITHUB_RUN_ID='122'))): + (raw / 'hosted.json').write_text(json.dumps(dict(schema=schema, source='a' * 40, + hosted_identity=recorded)), encoding='utf-8') + with patch.object(publisher, 'ROOT', root), \ + patch.object(publisher, 'hosted_identity', return_value=identity), self.assertRaises(ValueError): + publisher.publish(raw, output, experiment='cpu-boundary') + self.assertFalse(output.exists()) + self.assertFalse(output.with_name('public.partial').exists()) + + def test_non_hosted_entry_preserves_failure_without_building(self): + root = Path(__file__).resolve().parents[2] + (root / 'local').mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(dir=root / 'local') as folder: + output = Path(folder) / 'raw' + result = subprocess.run([sys.executable, '-m', 'experiments.measure.r54_cpu_hosted', '--out', str(output)], + cwd=root, env=dict(os.environ, GITHUB_ACTIONS='false'), + capture_output=True, timeout=15) + self.assertNotEqual(result.returncode, 0) + manifest = json.loads((output / 'hosted.json').read_text(encoding='utf-8')) + self.assertEqual(manifest['status'], 'failed') + self.assertIn('actual GitHub-hosted Windows job', manifest['error']) + self.assertFalse((output / 'bundle').exists()) + self.assertFalse((output / 'diagnostic').exists()) + + def test_branch_attempt_and_source_cannot_be_mixed(self): + from experiments.measure import r54_hosted as hosted + identity = dict(GITHUB_ACTIONS='true', RUNNER_ENVIRONMENT='github-hosted', RUNNER_OS='Windows', + GITHUB_REPOSITORY='JavaZeroo/termforge', GITHUB_EVENT_NAME='push', + GITHUB_REF='refs/heads/perf/grapheme-cpu-hosted-r54', GITHUB_RUN_ATTEMPT='1', + GITHUB_SHA='a' * 40) + with patch.dict(os.environ, identity, clear=True), patch.object(os, 'name', 'nt'), \ + patch.object(hosted, 'git', return_value='a' * 40): + self.assertEqual(hosted.hosted_identity('perf/grapheme-cpu-hosted-r54')['GITHUB_SHA'], 'a' * 40) + for branch in ('perf/grapheme-cpu-hosted-r54-setup2', 'perf/grapheme-cpu-hosted-r54-entry3'): + with patch.dict(os.environ, GITHUB_REF='refs/heads/' + branch): + self.assertEqual(hosted.hosted_identity(hosted.CPU_BOUNDARY_BRANCHES)['GITHUB_SHA'], 'a' * 40) + with self.assertRaises(ValueError): + hosted.hosted_identity() + with self.assertRaises(ValueError): + hosted.hosted_identity() + for key, value in (('GITHUB_RUN_ATTEMPT', '2'), ('GITHUB_SHA', 'b' * 40)): + with patch.dict(os.environ, {key: value}), self.assertRaises(ValueError): + hosted.hosted_identity('perf/grapheme-cpu-hosted-r54') + + def test_publication_keeps_failed_streams_and_separates_the_six_observation_scope(self): + from experiments.measure import r54_publish as publisher + identity = dict(GITHUB_RUN_ID='123', GITHUB_RUN_ATTEMPT='1', GITHUB_SHA='a' * 40) + with tempfile.TemporaryDirectory() as folder: + root = Path(folder).resolve() + raw = root / 'local/raw' + raw.mkdir(parents=True) + (raw / 'hosted.json').write_text(json.dumps(dict(schema='termforge-r54-cpu-hosted-v1', + source='a' * 40, hosted_identity=identity, status='failed')), encoding='utf-8') + record = raw / 'diagnostic/observations/000/record.json' + record.parent.mkdir(parents=True) + record.write_text(json.dumps(dict(child_pid=12345, status='failed', + child_cpu=dict(total_seconds=2.25), command=[str(root / 'tool.exe')])), encoding='utf-8') + payload = b'partial\xff\x00\n' + (record.parent / 'stdout.bin').write_bytes(payload) + for extra in ('diagnostic/observations/006/stdout.bin', 'batch-20261026/samples/000/stdout.bin', + 'diagnostic/observations/000/private.json', 'credentials.json'): + path = raw / extra + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b'do-not-publish') + output = root / 'local/public' + # Host and short-path queries are OS boundaries. No fixture claims + # a real hosted identity or CPU measurement. + with patch.object(publisher, 'ROOT', root), \ + patch.object(publisher, 'hosted_identity', return_value=identity), \ + patch.object(publisher, 'windows_short_path', side_effect=lambda path: path): + publisher.publish(raw, output, experiment='cpu-boundary') + publication = json.loads((output / 'publication.json').read_text(encoding='utf-8')) + self.assertEqual(publication['schema'], 'termforge-r54-cpu-hosted-publication-v1') + self.assertEqual({item['path'] for item in publication['artifacts']}, + {'hosted.json', 'diagnostic/observations/000/record.json', 'diagnostic/observations/000/stdout.bin'}) + published = json.loads((output / 'diagnostic/observations/000/record.json').read_text(encoding='utf-8')) + self.assertNotIn('child_pid', published) + self.assertEqual(published['child_cpu']['total_seconds'], 2.25) + self.assertEqual(published['status'], 'failed') + self.assertTrue(published['command'][0].startswith('${WORKSPACE}')) + self.assertEqual((output / 'diagnostic/observations/000/stdout.bin').read_bytes(), payload) + + +if __name__ == '__main__': + unittest.main() diff --git a/experiments/measure/test_r54_hosted.py b/experiments/measure/test_r54_hosted.py new file mode 100644 index 00000000..a66be4d7 --- /dev/null +++ b/experiments/measure/test_r54_hosted.py @@ -0,0 +1,96 @@ +"""Build input and publication boundaries; no performance workloads in tests.""" +import json +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[2] + + +class BuildBoundaryTests(unittest.TestCase): + def test_unreviewed_compiler_override_stops_before_source_checkout(self): + with tempfile.TemporaryDirectory() as folder: + output = Path(folder) / 'bundle' + result = subprocess.run( + [sys.executable, '-m', 'experiments.measure.r54_build', '--out', str(output)], + cwd=ROOT, env=dict(os.environ, RUSTFLAGS='-C opt-level=0'), + capture_output=True, timeout=20) + self.assertNotEqual(result.returncode, 0) + manifest = json.loads((output / 'preparation.json').read_text(encoding='utf-8')) + self.assertEqual(manifest['status'], 'failed') + self.assertIn('RUSTFLAGS', manifest['error']) + self.assertFalse((output / 'source-baseline').exists()) + self.assertFalse((output / 'plan-v1.json').exists()) + + +class PublicationBoundaryTests(unittest.TestCase): + def test_public_logs_redact_long_short_escaped_and_case_variants_without_changing_other_bytes(self): + from experiments.measure.r54_publish import replacement_forms, redact_paths + forms = replacement_forms([(r'C:\Users\runneradmin', '${PROFILE}'), + (r'C:\Users\RUNNER~1', '${PROFILE}')]) + original = (b'C:\\Users\\runneradmin\\Temp\\a C:\\\\Users\\\\RUNNER~1\\\\Temp\\\\b ' + b'c:/users/runner~1/Temp/c\xff\n') + self.assertEqual(redact_paths(original, forms), + b'${PROFILE}\\Temp\\a ${PROFILE}\\\\Temp\\\\b ${PROFILE}/Temp/c\xff\n') + + def test_interrupted_jsonl_tail_remains_visible_without_losing_complete_samples(self): + from experiments.measure.r54_publish import publish_jsonl + original = b'{"command":["/ci/work/tool.exe"],"cpu":2.0}\n{"command":[' + data, status = publish_jsonl(original, [('/ci/work', '${WORKSPACE}')]) + self.assertEqual(data, b'{"command":["${WORKSPACE}/tool.exe"],"cpu":2.0}\n{"command":[') + self.assertFalse(status['complete']) + self.assertIn('JSONDecodeError', status['error']) + self.assertEqual(status['error_line'], 2) + + def test_missing_or_different_raw_run_identity_is_rejected(self): + from experiments.measure.r54_publish import validate_raw_identity + identity = dict(GITHUB_RUN_ID='123', GITHUB_RUN_ATTEMPT='1', GITHUB_SHA='a' * 40) + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + with self.assertRaises(FileNotFoundError): + validate_raw_identity(root, identity) + path = root / 'hosted.json' + path.write_text(json.dumps(dict(schema='termforge-r54-hosted-v1', source='a' * 40, + hosted_identity=identity)), encoding='utf-8') + validate_raw_identity(root, identity) + with self.assertRaises(ValueError): + validate_raw_identity(root, dict(identity, GITHUB_RUN_ID='124')) + + def test_export_inventory_does_not_include_unrelated_or_arbitrary_files(self): + from experiments.measure.r54_publish import selected_paths + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + files = ['bundle/plan-v1.json', 'bundle/credentials.json', + 'batch-20261026/samples/000/stdout.bin', + 'batch-20261026/samples/000/unrelated.txt', + 'batch-20261026/samples/999/stdout.bin', 'personal-session.log'] + for name in files: + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b'fixture') + self.assertEqual({path.as_posix() for path in selected_paths(root)}, + {'bundle/plan-v1.json', 'batch-20261026/samples/000/stdout.bin'}) + + def test_environment_export_keeps_cpu_evidence_and_omits_process_identities(self): + from experiments.measure.r54_publish import public_environment + value = dict(status='rejected_busy_host', host=dict(platform='Windows', cpu_logical=4), + intervals=[dict(busy_percent=7.5, total_seconds=4, busy_seconds=.3)], + processes_before=[dict(pid=123, name='private-name', cpu_times=dict(user=2))], + processes_after=[dict(pid=123, name='private-name', cpu_times=dict(user=3))], + unexpected_private_field='do-not-publish') + result = public_environment(value) + self.assertEqual(result['intervals'], value['intervals']) + self.assertEqual(result['host']['cpu_logical'], 4) + self.assertEqual(result['status'], 'rejected_busy_host') + self.assertNotIn('private-name', json.dumps(result)) + self.assertNotIn('do-not-publish', json.dumps(result)) + self.assertNotIn('processes_before', result) + + + +if __name__ == '__main__': + unittest.main()