diff --git a/CMakeLists.txt b/CMakeLists.txt index b54b7f3b..886fc35d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,18 @@ termforge_warnings(text_action_policy_test) add_executable(core_measure experiments/measure/measure.cpp) target_link_libraries(core_measure PRIVATE termforge_core) termforge_warnings(core_measure) +foreach(target core_recovery_measure core_recovery_measure_noalloc) + add_executable(${target} experiments/measure/measure.cpp) + target_link_libraries(${target} PRIVATE termforge_core) + target_compile_definitions(${target} PRIVATE TERMFORGE_RECOVERY_MEASURE=1) + termforge_warnings(${target}) + if(WIN32) + target_compile_definitions(${target} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) + target_link_libraries(${target} PRIVATE Psapi) + endif() +endforeach() +target_compile_definitions(core_recovery_measure PRIVATE TERMFORGE_MEASURE_ALLOCATOR_WRAPPER=1) +target_compile_definitions(core_recovery_measure_noalloc PRIVATE TERMFORGE_MEASURE_ALLOCATOR_WRAPPER=0) if(WIN32) target_compile_definitions(core_measure PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) target_link_libraries(core_measure PRIVATE Psapi) @@ -312,6 +324,7 @@ if(BUILD_TESTING) termforge_add_python_test(windterm_raster_style_reference -m unittest research.ui.test_raster_style_reference -v) # Pure offline regressions only; OwnedNodeTests and frozen batches are never launched here. termforge_add_python_test(node_contract_export -m unittest research.ui.test_export_node_contract_deadline research.ui.test_node_contract_deadline.CheckpointTests research.ui.test_node_contract_deadline.LauncherVerdictTests -v) + termforge_add_python_test(recovery_measure_protocol -m unittest experiments.measure.test_recovery_measure -v) if(WIN32) termforge_add_python_test(node_contract_plan -m unittest research.ui.test_node_contract_deadline.PlanTests -v) endif() diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 3bbcfe52..2646d3a3 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -171,6 +171,7 @@ python -m unittest experiments.shell.test_workflow_sessions -v | 官方 WindTerm 生产端烟测 | [摘要](../benchmarks/results/2026-10-21-windterm-smoke/summary.json)、[基准规范](../benchmarks/PROTOCOL.md) | 7,732 字节写出、人工见到结束标记;没有全内容正确性、消费/呈现时间和正式重复批次,不是 C0 性能基线 | | 接收批量与通知合并 | [实验协议](../experiments/receive/README.md)、[120 次实验报告](research/receive-experiment-2026-10-22.md) | 原创固定输入、字节顺序、队列边界与背压;机制实验不等于 WindTerm 路径或端到端收益 | | 连续 CPU 区间与校准 | [计量协议和命令](../experiments/measure/README.md)、[批次 A](../experiments/measure/results/2026-10-23-v2-a/manifest.json)、[批次 B](../experiments/measure/results/2026-10-23-v2-b/manifest.json) | 48 个核心样本/4 次校准;尚缺解码/状态/历史独立归因、Unicode/大历史、无插桩对照和更严格环境控制 | +| 自主 C++ recovery 核心双构建准备 | [固定协议](../experiments/measure/RECOVERY-PREPARE.md)、[实际结果](../experiments/measure/results/2026-10-29-recovery-core-prepare/README.md)、[脱敏身份](../experiments/measure/results/2026-10-29-recovery-core-prepare/validation.json) | 同源 recovery 包装/无本工具包装的实际对象和命令核验;一次38进程含24短正确性/2identity/12拒绝,相关14项回归,25源/历史与18构建产物前后核对。旧strict/v2/calibration分列,不是Qt/ConPTY热路径、正式性能批次或数值预算 | | 输入到状态/提交 | [shell 端点定义](../experiments/shell/README.md)、[呈现端点定义](../experiments/presentation/README.md) | 现有脚本中断/后续命令时段与绘制提交有明确名称;不能当单键延迟或真实呈现,带完整画面检查的样本不是低扰动性能样本 | | 范围 | 结果与复现入口 | 可以支持的结论与限制 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index bf9b36fa..5a1f8621 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 区间校准与合成回归;候选字素改动的受限诊断中 project/interactive 吞吐中位数约下降 16%/9%,由 [#54](https://github.com/JavaZeroo/termforge/issues/54) 跟踪 | 共享宿主、allocator 包装与缺少真实呈现/输入时延端点使该诊断不构成正式预算。仍需同负载/同正确性、无插桩对照、观测开销和长期/多会话公平性;可靠原版基线后提出逐项预算及允许波动范围,**由用户明确确认**。与 WindTerm 的实际差距**待取证** | 当前优先;贯穿 R1–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 | | 大历史与动态内存压缩 | **未实现**产品能力;WindTerm 压缩属于官方性能声明,内部机制尚未证实 | 1/10/100 万行等明确规模下的内存/滚动/搜索/淘汰实验;根据瓶颈决定独立存储/压缩实现 | R4 | | 完整目标验收 | **待取证**:已有固定版本和局部观察,没有完整行为/性能对照矩阵 | 先在 Windows 11 x64 完整验收功能/行为/UI/性能及独立构建,再完成 Linux/macOS 全平台出口;重要差异和性能容差须有用户确认,Windows 通过不代表全平台完成;不依赖 WindTerm 二进制或专有资源 | R5 分平台出口 | diff --git a/experiments/measure/README.md b/experiments/measure/README.md index 8d474d53..0a8fabfa 100644 --- a/experiments/measure/README.md +++ b/experiments/measure/README.md @@ -1,5 +1,7 @@ # 核心连续区间计量与校准 +当前正式 C++ 路线另有 [recovery 双构建协议与命令](RECOVERY-PREPARE.md)及[一次限定准备结果](results/2026-10-29-recovery-core-prepare/README.md):同源的包装/无本工具 allocator 包装、实际对象/构建身份与短正确性;不复用下面旧 strict 样本作为 recovery 数据,不扩展历史 Alacritty 成本路线,也不构成 Qt/ConPTY 或 WindTerm 性能验收。 + `core_measure` 使用原创固定输入与独立状态检查。v2 修正了 v1 将很多短轮次 CPU 差值相加的计量问题;`compare.py` 另提供自研/候选 project 与 interactive 成本诊断。解码、屏幕更新、历史仍未完整独立归因,不能据此判断 WindTerm 性能差距。R2 流式字素成本的受控定位与预算由 [#54](https://github.com/JavaZeroo/termforge/issues/54)承接,完整产品性能和跨目标基线仍按 [R4 工作包 #9](https://github.com/JavaZeroo/termforge/issues/9)验收。 #54 的[测量前合同 v1](R54-COST.md)与 `r54_contract.py` 固定 `.3`/`.4` 源码、负载、相邻配对顺序、统计和预算决定程序;新增 Rust `--no-default-features` 测量构建可在编译期排除 allocator 包装。该准备仍缺实际两构建身份、受控重复测量和数值预算,不改变下述历史结果或性能边界。 diff --git a/experiments/measure/RECOVERY-PREPARE.md b/experiments/measure/RECOVERY-PREPARE.md new file mode 100644 index 00000000..e65adbef --- /dev/null +++ b/experiments/measure/RECOVERY-PREPARE.md @@ -0,0 +1,59 @@ +# 自主 C++ recovery 核心:双构建计量准备 + +本切片由 [#152](https://github.com/JavaZeroo/termforge/issues/152)承接,仅建立正确性和构建身份。正式路线见 ADR-0007/0008;性能容差尚未由用户确认。本工具不包含 Qt 事件调度、ConPTY、绘制或实际呈现,不称产品热路径基线或 WindTerm 性能等价。 + +## 构建与协议 + +三个可执行文件共享 `measure.cpp`。既有 `core_measure` 保留 strict、全局 allocator 包装、schema v2 及原 calibration;历史文件不修改。两个新增目标如下,源、编译/链接策略和核心参数相同;实际命令的目标路径/产物名自然不同,额外代码开关仅为 allocator 宏: + +| 目标 | Parser / 初始屏幕 / history | 本工具自定义 new/delete | +|---|---|---| +| `core_recovery_measure` | recovery / 24×80 / 100 | 编译在内 | +| `core_recovery_measure_noalloc` | recovery / 24×80 / 100 | 不定义 | + +这三个参数对应正式 Qt 的初始化,不代表包含 Qt 实际执行路径。新目标只接受 `project` / `interactive`;直接 state 路径未纳入新协议。`--identity` 先用非法 UTF-8 后接 ASCII 的独立小样例,实际核对 replacement、后继字符和 recovery 诊断;不是只返回一个模式标签。样例在计量区间外执行。 + +新协议为 `termforge-recovery-core-measure-v1`,明确记录 mode、网格、history、包装状态、route/chunk、负载及 observer 语义。旧 `calibrate` 在两个新目标均明确拒绝,不能用旧 runner 自动校准它们。 + +- observe=0:构造/前轮析构、feed、状态/历史维护与区间门槛;没有每轮投影。 +- observe=1:还包括每轮完整投影与哈希检查。包装构建同时计数普通 new;无包装构建的分配指标始终 `null`,不能解释为零分配。 +- 两种构建仍有计时、门槛、独立最终 oracle 和输出代码。关闭本工具 allocator 包装不证明整个 CRT 或产品没有插桩。 + +## 固定负载与区间 + +| route | 固定源字节 | SHA256 | 最终状态 FNV-1a64 | +|---|---|---|---| +| project | `(x×76 + CRLF)×1000`,78000B | `02ff60ca95c13ab3e41909685634ee6a800310dc39a07a98ac6965b0c57f2b14` | `5455070173120736870` | +| interactive | `(abcdefgh ESC[D ESC[D ESC[D Z BS Q CRLF)×1000`,22000B | `405d574c15d33d81e17745173dac95ed8c0082884e9692c71fbc67b5a588c148` | `8292075482910706113` | + +复用已有独立逐格、宽度、样式、光标、历史数量/淘汰预期及完整状态摘要。预热和最后一轮检查不移入计量区间。区间仍是一次 CPU 结束减开始、每8轮墙钟门槛及至少约250ms的 CPU 查询间隔;不累加短轮次、不估算扣除观察开销。最后一次析构在区间外,实际计入 `rounds-1` 次析构。输入准备、recovery 身份小样例、oracle 和序列化在区间外。 + +原 v2 与新协议不得拼接样本或汇总。正确性与共享核心代码可复用,旧 strict/包装数据不转记为 recovery 数据;parse/state 两路径也不相减解释净 parser 成本。`receive_seconds` 和 `presented_done` 始终显式 `null`。 + +## 预声明的单次短 sanity + +主代理释放构建/负载租约后,才运行此固定批次。此处记录执行前计划,不表示已经执行: + +```powershell +# 使用明确的 Release 构建输出;三个目标的实际编译参数和对象另外留证。 +cmake --build --config Release --target core_measure core_recovery_measure core_recovery_measure_noalloc +python experiments/measure/recovery_sanity.py --wrapped --noalloc --out +``` + +`recovery_sanity.py` 没有重复、随机调度或性能汇总参数。固定为以下 **38 个核心进程**,每个最多一次: + +1. 两个目标各一次 `--identity`,先全部通过才开始负载。 +2. 每目标 project 的 chunk=1/4096/65536、interactive 的 chunk=1/16/22000,均各 observe=0/1、CPU floor=0.1s,共24次短正确性样本。 +3. 每目标各拒绝一次 calibration、parse、state、chunk=0、observe=2、NaN duration,共12次反例。 + +floor=0.1s沿用既有短 sanity 参数,不是性能容差。核心内部连续区间上限仍60s,每个直接核心子进程外层期限90s;整批无独立硬时限,不将单进程期限称整程上限。不自动重试、补样、改负载或覆盖旧目录;首个身份/退出/字节/JSON/oracle/来源失败即停止。原始 stdout/stderr 先按字节保存,再解析和判定,保留失败及部分输出。此纯核心工具不产生子树,也不宣称原生 GUI/Job 验收。 + +旧 strict/v2/calibration 按现有 measurement 回归独立验证;其结果与新38进程批次分列。普通 CTest 仅新增纯协议测试,不自动执行新 sanity 或正式成本批次。 + +## 实际构建来源审计 + +构建前冻结 Git commit、CMake/三个核心头/测量源/校验器/执行器哈希,以及实际 generator、Release、架构、CRT、编译器/链接器路径、版本与可执行文件哈希。保留两份目标的完整真实编译/链接参数;仅本工具 wrapper 宏不同,不能依据目标名假定配置相同。 + +保留实际 `.obj` 与 EXE 的大小/SHA256。Windows 使用同一工具链的 `dumpbin /symbols <实际目标对象>`,核对全局普通/数组 `operator new` 的定义:包装对象应有本源定义,无包装对象不得有本源定义,外部 CRT 引用另列;不能只检查 `--identity` 的自报字段。对象路径从实际构建日志/项目文件取得,不猜路径。原始符号和绝对路径只留 local。 + +批次对源和两EXE逐次前后重算,完整批次后再核对全部冻结输入;对象/编译器身份由外层构建审计绑定,不由 runner 冒充已验证。公开前审查来源和隐私,仅发布合成负载与脱敏事实。正式空闲环境、重复测量、Qt调度/运输/绘制归因、原版对照和预算提案均另行准备。 diff --git a/experiments/measure/measure.cpp b/experiments/measure/measure.cpp index 6c343b5a..e44f46a1 100644 --- a/experiments/measure/measure.cpp +++ b/experiments/measure/measure.cpp @@ -10,13 +10,26 @@ #include #include #include +#include #ifdef _WIN32 #include #include #endif +#ifndef TERMFORGE_MEASURE_ALLOCATOR_WRAPPER +#define TERMFORGE_MEASURE_ALLOCATOR_WRAPPER 1 +#endif +static_assert(TERMFORGE_MEASURE_ALLOCATOR_WRAPPER == 0 || TERMFORGE_MEASURE_ALLOCATOR_WRAPPER == 1, + "allocator wrapper flag must be 0 or 1"); +static constexpr bool allocator_wrapper_compiled = TERMFORGE_MEASURE_ALLOCATOR_WRAPPER != 0; +#ifdef TERMFORGE_RECOVERY_MEASURE +static constexpr auto parser_mode = termforge::ParseMode::recovery; +#else +static constexpr auto parser_mode = termforge::ParseMode::strict; +#endif static bool count_allocations = false; static size_t allocations = 0, allocated_bytes = 0; +#if TERMFORGE_MEASURE_ALLOCATOR_WRAPPER void* operator new(size_t size) { if (void* p = std::malloc(size ? size : 1)) { if (count_allocations) { ++allocations; allocated_bytes += size; } @@ -29,6 +42,7 @@ void operator delete(void* p, size_t) noexcept { std::free(p); } void* operator new[](size_t size) { return ::operator new(size); } void operator delete[](void* p) noexcept { ::operator delete(p); } void operator delete[](void* p, size_t) noexcept { ::operator delete(p); } +#endif using Clock = std::chrono::steady_clock; static constexpr double cpu_poll_seconds = .25; static double cpu_seconds() { @@ -109,6 +123,28 @@ static uint64_t hash_state(const termforge::Screen& screen) { return hash; } +#ifdef TERMFORGE_RECOVERY_MEASURE +static uint64_t hash_payload(std::string_view bytes) { + uint64_t hash = 14695981039346656037ull; + for (unsigned char c : bytes) { hash ^= c; hash *= 1099511628211ull; } + return hash; +} +// Exercise the selected parser, outside every measured interval. A build that +// merely reports recovery while constructing a strict parser must fail here. +static void check_recovery_mode() { + termforge::Parser probe(1, 3, 0, parser_mode); + probe.feed(std::string_view("\xff" "A", 2)); probe.finish(); + if (probe.screen.lines[0][0].text != "\xef\xbf\xbd" || probe.screen.lines[0][1].text != "A" || + probe.screen.column != 2 || probe.diagnostics().malformed_utf8 != 1) + throw std::runtime_error("recovery parser identity probe failed"); +} +static void recovery_fields(const char* kind) { + std::cout << "\"protocol\":\"termforge-recovery-core-measure-v1\",\"kind\":\"" << kind + << "\",\"parse_mode\":\"recovery\",\"rows\":24,\"columns\":80,\"history_limit\":100" + << ",\"allocator_wrapper_compiled\":" << (allocator_wrapper_compiled ? "true" : "false") + << ",\"scope\":\"core replay only\",\"recovery_probe_passed\":true"; +} +#else static void calibrate(double duration, bool on_first) { // Repeated clock-read loop: includes two wall reads, CPU read, bookkeeping // and periodic gates. Its average is NOT an isolated GetProcessTimes cost. @@ -163,21 +199,40 @@ static void calibrate(double duration, bool on_first) { } std::cout << "],\"counter_off_is_uninstrumented\":false,\"calibration_is_subtracted\":false}\n"; } +#endif int main(int argc, char** argv) { try { std::cout << std::setprecision(17); +#ifdef TERMFORGE_RECOVERY_MEASURE + if (argc == 2 && std::string(argv[1]) == "--identity") { + check_recovery_mode(); + std::cout << '{'; recovery_fields("identity"); + std::cout << ",\"routes\":[\"project\",\"interactive\"]," + "\"observe_0\":\"feed without per-round projection\"," + "\"observe_1\":\"feed with per-round projection and hash\"}\n"; + return 0; + } + if (argc >= 2 && std::string(argv[1]) == "calibrate") + throw std::runtime_error("legacy allocation calibration is unavailable in the recovery protocol"); + if (argc != 5) throw std::runtime_error("recovery measure: --identity OR project|interactive chunk observe(0|1) min_cpu_seconds"); +#else if ((argc == 3 || argc == 4) && std::string(argv[1]) == "calibrate") { calibrate(duration_arg(argv[2]), argc == 4 && integer_arg(argv[3], 1) != 0); return 0; } if (argc != 5) throw std::runtime_error("core_measure parse|project|interactive|state chunk observe(0|1) min_cpu_seconds OR calibrate min_cpu_seconds [observe_first(0|1)]"); +#endif const std::string route = argv[1]; const size_t chunk = integer_arg(argv[2], 1048576); const bool observe = integer_arg(argv[3], 1) != 0; const double duration = duration_arg(argv[4]); if ((route != "parse" && route != "project" && route != "interactive" && route != "state") || chunk == 0) throw std::runtime_error("invalid arguments"); +#ifdef TERMFORGE_RECOVERY_MEASURE + if (route != "project" && route != "interactive") throw std::runtime_error("unsupported recovery replay route"); + check_recovery_mode(); +#endif const std::string line(76, 'x'); const std::string action = "abcdefgh\x1b[D\x1b[D\x1b[DZ\bQ\r\n"; std::string payload; @@ -220,7 +275,7 @@ int main(int argc, char** argv) { const auto oracle_start = Clock::now(); uint64_t expected_hash = 0; { - termforge::Parser warmup; run(warmup); + termforge::Parser warmup(24, 80, 100, parser_mode); run(warmup); if (!correct(warmup.screen)) throw std::runtime_error("warmup oracle failed"); expected_hash = hash_state(warmup.screen); } @@ -229,9 +284,9 @@ int main(int argc, char** argv) { // final state stays alive for the oracle OUTSIDE the measured interval. std::optional parser; allocations = allocated_bytes = 0; - count_allocations = observe; + count_allocations = observe && allocator_wrapper_compiled; const auto interval = measure(duration, 8, [&] { - parser.emplace(); run(*parser); + parser.emplace(24, 80, 100, parser_mode); run(*parser); if ((route == "project" || route == "interactive") && observe && hash_state(parser->screen) != expected_hash) throw std::runtime_error("measured projection oracle failed"); }); @@ -245,7 +300,15 @@ int main(int argc, char** argv) { if (!GetProcessMemoryInfo(GetCurrentProcess(), &memory, sizeof(memory))) throw std::runtime_error("memory query failed"); peak = memory.PeakWorkingSetSize; #endif +#ifdef TERMFORGE_RECOVERY_MEASURE + std::cout << '{'; recovery_fields("core_cost"); + std::cout << ",\"valid\":true,\"workload_id\":\"" << (route == "project" ? "ascii-project-78000-v1" : "ascii-interactive-22000-v1") + << "\",\"payload_fnv1a64\":\"" << hash_payload(payload) + << "\",\"observer_semantics\":\"" << (observe ? "feed with per-round projection and hash" : "feed without per-round projection") + << "\",\"route\":\"" << route << "\",\"chunk\":" << chunk +#else std::cout << "{\"schema_version\":2,\"kind\":\"core_cost\",\"valid\":true,\"route\":\"" << route << "\",\"chunk\":" << chunk +#endif << ",\"observe\":" << observe << ",\"rounds\":" << interval.iterations << ",\"bytes_per_round\":" << payload.size() << ",\"cpu_floor_seconds\":" << duration << ','; interval_json(interval, 8); std::cout << ','; clock_identity(); @@ -253,13 +316,16 @@ int main(int argc, char** argv) { << ",\"oracle_scope\":\"" << ((route == "project" || route == "interactive") && observe ? "warmup and final; every measured projection also hashed" : "warmup and final iteration") << "\"" << ",\"state_fnv1a64\":\"" << expected_hash << "\",\"ordinary_allocations\":"; - if (observe) std::cout << allocations; else std::cout << "null"; + if (observe && allocator_wrapper_compiled) std::cout << allocations; else std::cout << "null"; std::cout << ",\"ordinary_allocated_bytes\":"; - if (observe) std::cout << allocated_bytes; else std::cout << "null"; + if (observe && allocator_wrapper_compiled) std::cout << allocated_bytes; else std::cout << "null"; std::cout << ",\"peak_working_set_bytes\":"; if (peak) std::cout << peak; else std::cout << "null"; std::cout << ",\"cpu_interval_differences\":1,\"counted_parser_destructions\":" << interval.iterations - 1 - << ",\"counter_off_is_uninstrumented\":false,\"receive_seconds\":null,\"presented_done\":null}\n"; +#ifndef TERMFORGE_RECOVERY_MEASURE + << ",\"counter_off_is_uninstrumented\":false" +#endif + << ",\"receive_seconds\":null,\"presented_done\":null}\n"; return 0; } catch (const std::exception& e) { count_allocations = false; std::cerr << e.what() << '\n'; return 2; } } diff --git a/experiments/measure/recovery.py b/experiments/measure/recovery.py new file mode 100644 index 00000000..55fce50f --- /dev/null +++ b/experiments/measure/recovery.py @@ -0,0 +1,110 @@ +"""Strict public contract for the separate recovery-core replay diagnostic.""" +from __future__ import annotations +import json +import math + +PROTOCOL = "termforge-recovery-core-measure-v1" +WORKLOADS = { + "project": {"id": "ascii-project-78000-v1", "bytes": 78000, + "payload_fnv1a64": "13372643718718663029", "state_fnv1a64": "5455070173120736870"}, + "interactive": {"id": "ascii-interactive-22000-v1", "bytes": 22000, + "payload_fnv1a64": "14158139299817394597", "state_fnv1a64": "8292075482910706113"}, +} + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def _common(value, wrapper, kind): + require(type(value) is dict and type(wrapper) is bool, "Expected typed identity") + expected = { + "protocol": PROTOCOL, "kind": kind, "parse_mode": "recovery", + "rows": 24, "columns": 80, "history_limit": 100, + "allocator_wrapper_compiled": wrapper, "scope": "core replay only", + "recovery_probe_passed": True, + } + for name, expected_value in expected.items(): + actual = value.get(name) + require(type(actual) is type(expected_value) and actual == expected_value, + f"Recovery identity differs: {name}") + + +def validate_identity(value, wrapper): + """Reject the wrong parser, layout or allocator build before starting work.""" + _common(value, wrapper, "identity") + require(value.get("routes") == ["project", "interactive"], "Recovery routes differ") + require(value.get("observe_0") == "feed without per-round projection" and + value.get("observe_1") == "feed with per-round projection and hash", + "Observer semantics differ") + + +def _integer(value, minimum=0): + return type(value) is int and value >= minimum + + +def _finite(value, minimum=0): + return type(value) in (int, float) and math.isfinite(value) and value >= minimum + + +def validate_sample(value, wrapper, route, chunk, observe, floor): + """Validate one fixed replay; this does not judge speed or the Qt product.""" + require(type(route) is str and route in WORKLOADS and _integer(chunk, 1) and chunk <= 1048576 and + type(observe) is int and observe in (0, 1) and _finite(floor, .1) and floor <= 10, + "Invalid requested recovery sample") + _common(value, wrapper, "core_cost") + require(value.get("valid") is True, "Recovery oracle did not pass") + workload = WORKLOADS[route] + expected = {"route": route, "chunk": chunk, "observe": observe, + "bytes_per_round": workload["bytes"], "workload_id": workload["id"], + "payload_fnv1a64": workload["payload_fnv1a64"], "state_fnv1a64": workload["state_fnv1a64"], + "observer_semantics": "feed with per-round projection and hash" if observe else "feed without per-round projection", + "oracle_scope": "warmup and final; every measured projection also hashed" if observe else "warmup and final iteration"} + for name, expected_value in expected.items(): + actual = value.get(name) + require(type(actual) is type(expected_value) and actual == expected_value, + f"Recovery sample differs: {name}") + require(_finite(value.get("cpu_floor_seconds"), .1) and value["cpu_floor_seconds"] == floor, + "CPU floor differs") + require(all(_finite(value.get(name), floor) for name in ("interval_cpu_seconds", "interval_wall_seconds")), + "Continuous interval did not meet the requested floor") + require(all(_integer(value.get(name), 1) for name in ("rounds", "iterations", "wall_gate_polls", "cpu_gate_polls")), + "Missing interval progress") + require(value["iterations"] == value["rounds"] == 8 * value["wall_gate_polls"] and + type(value.get("wall_gate_every_iterations")) is int and value["wall_gate_every_iterations"] == 8 and + type(value.get("cpu_interval_differences")) is int and value["cpu_interval_differences"] == 1 and + _integer(value.get("counted_parser_destructions")) and + value["counted_parser_destructions"] == value["rounds"] - 1 and + _finite(value.get("cpu_gate_min_wall_spacing_seconds")) and value["cpu_gate_min_wall_spacing_seconds"] == .25, + "Continuous interval endpoint differs") + require(all(_finite(value.get(name)) for name in ("warmup_and_oracle_seconds", "final_oracle_seconds")), + "Invalid outside-interval timing") + require(value.get("cpu_clock") in ("GetProcessTimes kernel plus user", "std::clock process CPU") and + value.get("wall_clock") == "std::chrono::steady_clock" and value.get("wall_clock_is_steady") is True and + all(_finite(value.get(name)) and value[name] > 0 for name in + ("cpu_representation_unit_seconds", "wall_representation_unit_seconds")), + "Clock identity differs") + for name in ("ordinary_allocations", "ordinary_allocated_bytes"): + require(name in value and (_integer(value[name], 1) if wrapper and observe else value[name] is None), + "Allocation metric does not match this observer/build") + require("peak_working_set_bytes" in value and + (value["peak_working_set_bytes"] is None or _integer(value["peak_working_set_bytes"], 1)), + "Invalid process peak memory") + require(all(name in value and value[name] is None for name in ("receive_seconds", "presented_done")), + "Unmeasured endpoint must be explicit unknown") + + +def decode_response(data): + """Parse the exact stdout bytes without replacing UTF-8 or duplicate keys.""" + def object_pairs(pairs): + value = {} + for name, item in pairs: + require(name not in value, "Duplicate response key") + value[name] = item + return value + + def constant(_): + raise ValueError("Non-finite JSON number") + + return json.loads(data.decode("utf-8"), object_pairs_hook=object_pairs, parse_constant=constant) diff --git a/experiments/measure/recovery_sanity.py b/experiments/measure/recovery_sanity.py new file mode 100644 index 00000000..c2c1d969 --- /dev/null +++ b/experiments/measure/recovery_sanity.py @@ -0,0 +1,126 @@ +"""One fixed correctness-only batch for the two recovery replay executables. + +This is not a performance runner: there are no repetitions, summaries or speed +thresholds. Raw streams precede validation and output directories are never reused. +""" +from __future__ import annotations +import argparse +import hashlib +import json +from pathlib import Path +import subprocess +import sys + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +from experiments.measure.recovery import decode_response, require, validate_identity, validate_sample + +FLOOR = .1 +PROCESS_TIMEOUT = 90 +CHUNKS = {"project": (1, 4096, 65536), "interactive": (1, 16, 22000)} +INVALID = (("calibrate", ".1"), ("parse", "4096", "0", ".1"), + ("state", "4096", "0", ".1"), ("project", "0", "0", ".1"), + ("project", "4096", "2", ".1"), ("project", "4096", "0", "nan")) +SOURCES = ("CMakeLists.txt", "experiments/core/terminal.hpp", "experiments/core/screen.hpp", + "experiments/core/unicode_width.hpp", "experiments/measure/measure.cpp", + "experiments/measure/recovery.py", "experiments/measure/recovery_sanity.py") + + +def digest(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def run_sanity(wrapped, noalloc, out): + binaries = ((True, wrapped.resolve(strict=True)), (False, noalloc.resolve(strict=True))) + require(binaries[0][1] != binaries[1][1], "Distinct build paths are required") + binary_hashes = {str(path): digest(path) for _, path in binaries} + source_hashes = {name: digest(ROOT / name) for name in SOURCES} + out.mkdir(parents=True, exist_ok=False) + manifest = {"protocol": "termforge-recovery-core-sanity-v1", "status": "running", + "maximum_attempts": 1, "planned_core_processes": 38, "attempted_core_processes": 0, + "fixed_floor_seconds": FLOOR, "per_process_timeout_seconds": PROCESS_TIMEOUT, + "whole_batch_bound_seconds": None, "performance_gate": "not_evaluated", + "source_sha256": source_hashes, "binary_sha256": binary_hashes, "records": []} + + def save(): + (out / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8", newline="\n") + + def unchanged(): + require(all(digest(path) == binary_hashes[str(path)] for _, path in binaries) and + all(digest(ROOT / name) == value for name, value in source_hashes.items()), + "Frozen build or source changed") + + def invoke(path, arguments, wrapper, expected=None): + unchanged() + index = len(manifest["records"]) + row = {"index": index, "wrapper": wrapper, "arguments": list(arguments), + "exit_code": None, "timed_out": False, "status": "failed"} + manifest["attempted_core_processes"] += 1 + save() + try: + result = subprocess.run([str(path), *arguments], stdin=subprocess.DEVNULL, + capture_output=True, timeout=PROCESS_TIMEOUT) + stdout, stderr = result.stdout, result.stderr + row["exit_code"] = result.returncode + except subprocess.TimeoutExpired as error: + stdout, stderr = error.stdout or b"", error.stderr or b"" + row["timed_out"] = True + except OSError: + stdout = stderr = b"" + row["launch_failed"] = True + for name, data in (("stdout", stdout), ("stderr", stderr)): + target = out / f"{index:02d}.{name}.bin" + target.write_bytes(data) + row[name] = {"file": target.name, "bytes": len(data), "sha256": digest(target)} + manifest["records"].append(row) + save() + unchanged() + require(not row["timed_out"], "Core sanity timed out; no retry") + if expected == "rejected": + require(row["exit_code"] == 2 and not stdout and bool(stderr), "Invalid CLI was not rejected") + else: + require(row["exit_code"] == 0 and not stderr, "Core sanity failed") + value = decode_response(stdout) + if expected is None: + validate_identity(value, wrapper) + else: + validate_sample(value, wrapper, *expected, FLOOR) + row["status"] = "passed" + save() + + save() + try: + for wrapper, path in binaries: + invoke(path, ("--identity",), wrapper) + for wrapper, path in binaries: + for route, chunks in CHUNKS.items(): + for chunk in chunks: + for observe in (0, 1): + invoke(path, (route, str(chunk), str(observe), str(FLOOR)), wrapper, + (route, chunk, observe)) + for arguments in INVALID: + invoke(path, arguments, wrapper, "rejected") + unchanged() + require(len(manifest["records"]) == 38, "Fixed sanity process count differs") + manifest["status"] = "passed" + except BaseException: + manifest["status"] = "failed" + raise + finally: + save() + return manifest + + +def main(): + cli = argparse.ArgumentParser(description=__doc__) + cli.add_argument("--wrapped", type=Path, required=True) + cli.add_argument("--noalloc", type=Path, required=True) + cli.add_argument("--out", type=Path, required=True) + args = cli.parse_args() + result = run_sanity(args.wrapped, args.noalloc, args.out) + print(json.dumps({"status": result["status"], "core_processes": len(result["records"]), + "performance_gate": result["performance_gate"]})) + + +if __name__ == "__main__": + main() diff --git a/experiments/measure/results/2026-10-29-recovery-core-prepare/README.md b/experiments/measure/results/2026-10-29-recovery-core-prepare/README.md new file mode 100644 index 00000000..5eea4485 --- /dev/null +++ b/experiments/measure/results/2026-10-29-recovery-core-prepare/README.md @@ -0,0 +1,17 @@ +# C++ recovery 核心双构建:限定准备结果 + +[#152](https://github.com/JavaZeroo/termforge/issues/152)的实现源为 `7721985399a14667d564811029cd8c93308bcb36`,基点 `05f0065f4eb19ce5a76065d18f2247bcc731e285`。交付同源码 recovery 包装/无自定义 allocator 包装两个目标、独立协议、严格正确性校验和固定短 sanity;既有 strict `core_measure` 的 v2/calibration 保留。[协议、固定输入及执行前计划](../../RECOVERY-PREPARE.md),[脱敏完整记录](validation.json)。 + +## 实际验证与来源 + +- 一次 Visual Studio 17 2022 x64 Release 构建三个目标成功,MSVC19.42.34433.0、`/MD /O2 /Ob2 /std:c++17`;三个构建均0警告/0错误。两份 recovery 的真实 compile/link tlog,在仅归一目标路径/产物名及 wrapper 宏后相同;没有依据目标名推测配置。 +- `dumpbin /symbols` 核对实际对象:旧 strict 和新 recovery 包装对象有普通/数组 `operator new` 的本源定义;noalloc 对象均无本源定义,普通 `new` 是外部引用,数组 `new` 未出现。它仅证明本工具包装有无,不说明 CRT 或产品整体无插桩。编译器、linker、dumpbin、对象、EXE、命令和符号输出均保存身份。 +- 旧 strict 的 `--identity` 仅执行一次,exit2、stdout空,为新入口缺失的真实CLI红例;未改变旧CLI。新协议的普通5项纯测试通过;两次早期公开 seam 红例分别是尚无模块及尚无 sample validator,日志原件保留,未伪称旧性能退化。 +- 冻结 plan SHA256 `e26f714c6c5a97962c1be72c573e373398edddb48ca54b0ab73a5d9fe9217ddc` 的**唯一38进程批次通过**:两个实际 recovery 身份小样例、24个固定正确性样本、12个预期 CLI 拒绝。project chunk1/4096/65536、interactive chunk1/16/22000、每目标均observe0/1及CPU floor0.1s。没有重试、补样或改变原件。 +- 全部样本的独立格位、宽度/样式、光标、100行历史、淘汰数和最终摘要通过。observe1还执行逐轮投影/哈希;包装+observe1真实计数,无包装两种observe均为null。身份小样例实际处理非法UTF-8及后继ASCII,拒绝只更换标签的strict构建。 +- 新协议与旧 `test_measure` 相关回归同次 **14/14、3.723s**;旧strict/v2/实际calibration、坏JSON/退出/超时保留及新协议拒绝分列,没有运行完整default/Qt矩阵。测试耗时不是核心性能指标。 +- 执行源7文件与18份历史结果前后逐字节一致;18项实际构建产物/工具/命令日志后核对一致。单批raw manifest SHA256 `4a978f34bae06be0f761e8a8b9bfa9cdf151c6e331cc7b8e573098f3d462ba22`。全部76份新stdout/stderr流先保存原字节再验证;公开仅保留字节身份及固定正确性投影,区间/进程/构建原件留local。 + +这是共享 Windows11 build26200/UBR9711 宿主的构建和短正确性准备,不是受控性能样本。CPU floor0.1s沿用短sanity参数,不是用户批准的性能门槛;每进程外90s、内部区间60s,整批独立硬上限null,预算未放宽。 + +新工具仍包含区间门槛、准备、oracle和输出代码;observe1包含真实投影成本。初始recovery/24×80/history100对应Qt初始化,仅复用该核心参数;本工具不包含Qt事件循环、10ms drain、ConPTY、输入延迟、绘制或呈现。`receive_seconds/presented_done=null`,正式性能样本0,`performance_gate=not_evaluated`。未计算吞吐排名/回归百分比,未验收#54/#34、WindTerm等价或R2;未来正式空闲批次及数值容差另行声明并由用户确认。 diff --git a/experiments/measure/results/2026-10-29-recovery-core-prepare/validation.json b/experiments/measure/results/2026-10-29-recovery-core-prepare/validation.json new file mode 100644 index 00000000..df140344 --- /dev/null +++ b/experiments/measure/results/2026-10-29-recovery-core-prepare/validation.json @@ -0,0 +1,1227 @@ +{ + "schema": "termforge-recovery-core-prepare-validation-v1", + "issue": 152, + "implementation_commit": "7721985399a14667d564811029cd8c93308bcb36", + "base_commit": "05f0065f4eb19ce5a76065d18f2247bcc731e285", + "plan_sha256": "e26f714c6c5a97962c1be72c573e373398edddb48ca54b0ab73a5d9fe9217ddc", + "protocol": "termforge-recovery-core-measure-v1", + "parse_mode": "recovery", + "rows": 24, + "columns": 80, + "history_limit": 100, + "sanity_batch_count": 1, + "core_processes": 38, + "successful_identity_processes": 2, + "correctness_samples": 24, + "expected_cli_rejections": 12, + "legacy_identity_red": { + "attempts": 1, + "exit_code": 2, + "observation": "legacy strict CLI does not implement new identity protocol" + }, + "pure_tests_passed": 5, + "related_tests_passed": 14, + "related_tests_seconds": 3.723, + "full_matrix_executed": false, + "ci_retries": 0, + "formal_performance_samples": 0, + "performance_gate": "not_evaluated", + "whole_batch_bound_seconds": null, + "per_process_timeout_seconds": 90, + "internal_interval_budget_seconds": 60, + "sanity_cpu_floor_seconds": 0.1, + "environment": { + "platform": "Windows-11-10.0.26200-SP0", + "python": "3.12.10", + "ubr": 9711, + "shared_host": true + }, + "build": { + "generator": "Visual Studio 17 2022", + "architecture": "x64", + "configuration": "Release", + "msvc_runtime": "MultiThreadedDLL", + "qt": false, + "candidate": false, + "targets": [ + "core_measure", + "core_recovery_measure", + "core_recovery_measure_noalloc" + ] + }, + "compiler_version": "19.42.34433.0", + "tools": { + "cl.exe": { + "sha256": "42fd88579adc0e2418a0a71ec9adcfcf677d39ff2724c9d6ac9d901264812bf2" + }, + "link.exe": { + "sha256": "483550657299153a48e557a7db1ba26c52603fb48fc19b1fe1e5870ec96dacb4" + }, + "dumpbin.exe": { + "sha256": "9ea5c7faa7c24f9a333bc3ffd1113329c633b0256cfb9efb9ae587b9065bf763" + } + }, + "source_sha256": { + "CMakeLists.txt": "630db31cbec1a99162edb0a4821fc3ab76583e83c90a6716506c9fd9110538af", + "experiments/core/terminal.hpp": "ed3c030611a6df38df26057a8c2834f2808cd05306e73aead79927b1f76fe5ca", + "experiments/core/screen.hpp": "04c19f09ebc0c468efbef8dd4c9da8f4bcd44e94000a87c56239756c0df1b474", + "experiments/core/unicode_width.hpp": "d48bf60090a9c6a4263bb46ac587b13814b52c0dbe9d2649cee20e832a1e1926", + "experiments/measure/measure.cpp": "45f582335960983f6ead59e5f01dda38e1e0a3eeb3ca4405b0dfeccb2bcac434", + "experiments/measure/recovery.py": "e4bbc2aecc3253d8cd1b7cda7fd73f3125bae5d38ed3f17fdab5525d28973f2b", + "experiments/measure/recovery_sanity.py": "c6230fcff13c728c685bdc11185b640a5b30e9b7be152885e1a2afcb4cf1d084" + }, + "historical_files_verified": 18, + "source_and_historical_files_verified_after": 25, + "build_artifacts_verified_after": 18, + "twins_normalized_compile_equal": true, + "twins_normalized_link_equal": true, + "targets": { + "core_measure": { + "object": { + "bytes": 286883, + "sha256": "bf3bc5cc6cdbbdef03e0419a19684dffd7d1678cfb9ce8278786f9dc375bfffa" + }, + "exe": { + "bytes": 87040, + "sha256": "b2a8070daea27998d486839cb3e79e27c202aa7fc95d087ca56306dec95e6775" + }, + "symbols": { + "bytes": 358476, + "sha256": "133bb7d2c49de566b7b0f1830f5719d77126cf8e64ef5a7e0c59e69acd89718b" + }, + "new_symbols": { + "ordinary": { + "definition_in_object": true, + "external_reference": false + }, + "array": { + "definition_in_object": true, + "external_reference": false + } + }, + "compile_command_sha256": "d0746079bd7e1c96b5afa5ba1e73b44cff437f0400a150c47314bff1671d19ed", + "link_command_sha256": "3149d5936444bff868f75a4c263cae1c9014ee43bf436ffef3f77fbed1dbf3e2" + }, + "core_recovery_measure": { + "object": { + "bytes": 284362, + "sha256": "d208854391c13af730bf6d50ddcb9474d70831186ec944f8d1984fe7b0e17aa2" + }, + "exe": { + "bytes": 86528, + "sha256": "4c0f907572cda3e57e2ebfab1ec6bf5d92eb10c63ecf517fa22228a5fde5c535" + }, + "symbols": { + "bytes": 356729, + "sha256": "f0957ad945e45ee2b89238d2df157c61d34477ac0e34da1e8936458432637c1c" + }, + "new_symbols": { + "ordinary": { + "definition_in_object": true, + "external_reference": false + }, + "array": { + "definition_in_object": true, + "external_reference": false + } + }, + "compile_command_sha256": "139244d0a659fa04b6b199dc3af09d940bff1f89844fd7fc2139259c98d06312", + "link_command_sha256": "99aa8e271aee95d216cb476fd7778836f7adeca44915143ff687e4c2016595d4" + }, + "core_recovery_measure_noalloc": { + "object": { + "bytes": 279239, + "sha256": "eeab48bc37bc94c70e9b9e1fb8c45c616563f88e4b22852c4a47c62d94c7effe" + }, + "exe": { + "bytes": 86528, + "sha256": "15ee46b8f9f471ad93af98907cd2424642ec8bd12511c5c1a3f9d0969a79b619" + }, + "symbols": { + "bytes": 349052, + "sha256": "312b1dd3f90d48dd1f5a514a5eb5b1e11d5bdc1246d947abedfde4a04f8d69c6" + }, + "new_symbols": { + "ordinary": { + "definition_in_object": false, + "external_reference": true + }, + "array": { + "definition_in_object": false, + "external_reference": false + } + }, + "compile_command_sha256": "3ef45824de22959a5d4f5181be13e15b4bf19b693d3b7b077bbf1e0b7b7cf6fe", + "link_command_sha256": "ed22da9c0d6ee9c2537267be722d0dde00f85d7145559cd32a7465c279a39565" + } + }, + "raw_sanity_manifest_sha256": "4a978f34bae06be0f761e8a8b9bfa9cdf151c6e331cc7b8e573098f3d462ba22", + "build_audit_before_sha256": "3b5a0e410707559b1e92fdf44bc518694f0243ed3e190e8f0877c048f6a35edf", + "build_audit_after_sha256": "3cadf271e40f865d428ff2633d06917418d0d96f7b3534774e8c15124114f568", + "private_log_identities": { + "configure": { + "bytes": 747, + "sha256": "eb56ea578ab494f8046e2dae919c8a29251a71be0e9a2f8af8dc6a937b4eacca" + }, + "build": { + "bytes": 5355178, + "sha256": "2d1772c76f138747a22bf3714075fe5fe86f8fdfac890abff461ec490220e7af" + }, + "legacy-red-stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "legacy-red-stderr": { + "bytes": 131, + "sha256": "34bd906f6d6925d6e2f44b14e5765ff709a2a8fd268546b32344f679083e73d7" + }, + "related-tests-stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "related-tests-stderr": { + "bytes": 2498, + "sha256": "ab9d6190e61da93e45b41597edb3675010cea7457c490bf13619db5fa653f3a0" + }, + "identity-red": { + "bytes": 980, + "sha256": "17ae3bb5ef65c9fb1c763ca92d55ada155005a4642b68e7e726e9321e60a7071" + }, + "sample-red": { + "bytes": 1087, + "sha256": "3fbd18f3bed52c2ea8abd9560a7c2cb7a0b0ae321bc0ac39ee94abc52c3dbfd2" + }, + "pure-final": { + "bytes": 1122, + "sha256": "9958ded0b53314e60c1746656c69e3267ebf9cb849fd534ab8ff6769af759382" + } + }, + "records": [ + { + "index": 0, + "allocator_wrapper_compiled": true, + "arguments": [ + "--identity" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 363, + "sha256": "198dc94bc5fe3340fc4eb323b5e239fb79cc90b1534d4ab95123362ec93be9f7" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "recovery build identity verified" + }, + { + "index": 1, + "allocator_wrapper_compiled": false, + "arguments": [ + "--identity" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 364, + "sha256": "51e3e1be4824ae86163368e7922ecbc854477175e7c73f15668ec3bf636bfbb6" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "recovery build identity verified" + }, + { + "index": 2, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "1", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1275, + "sha256": "933b485feb8ddb92e3f93a76835f42daf8622643b852ca5854ae9d19401bf905" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 1, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 3, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "1", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1325, + "sha256": "cff2cd2ba9e8d74eae1b8cba965675edd7dfef98e155138dbee9f7a8108745c1" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 1, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 4, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "4096", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1281, + "sha256": "ba678bcad51606a82cfe2123557fa8cf56b7205eed121077471841ea2aec2aff" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 4096, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 5, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "4096", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1320, + "sha256": "8627d9e313d98bcfd4d5d15ecb7c869c216ccfb90656a3e93183f5341c39cb53" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 4096, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 6, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "65536", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1282, + "sha256": "3b239a335da12ee86f00188a76bb861cf602e3174c9f016b50e642a8fc50127e" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 65536, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 7, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "65536", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1329, + "sha256": "de46e317ad804a947d19d626bd0831b07db870f7da0896fd2f5513a03e7f9eba" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 65536, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 8, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "1", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1295, + "sha256": "464246e5cda8c194bdcedb22a748dc05cdd187a53b803ad11bc1f4cc077bc091" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 1, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 9, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "1", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1337, + "sha256": "c39a5e1d791efae1562c2174131e594654a37f3ed625b400b860e88ac768efe1" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 1, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 10, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "16", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1296, + "sha256": "156a775fabebca9d2f767ddf41ffa956a79beb79bdd6534b4bfb812d34d089fd" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 16, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 11, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "16", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1330, + "sha256": "3490b8513aeba2bbc62647250d24842a9d0080efb06dd82a54d8a79a76f93e29" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 16, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 12, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "22000", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1303, + "sha256": "d368451dbc208da520cbc0dd9e5439231e09b8848a3e7f7d1a381dffeb5f87f5" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 22000, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 13, + "allocator_wrapper_compiled": true, + "arguments": [ + "interactive", + "22000", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1341, + "sha256": "fe3cc840c727cee270462ed2d2780d321f0142a567987eab8c5e2aa1e8e261ea" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 22000, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "recorded", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 14, + "allocator_wrapper_compiled": true, + "arguments": [ + "calibrate", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 71, + "sha256": "375f5c6850053495ecbecb0f75d62c77ee138901411caea56c78d41522806ed8" + }, + "observation": "expected CLI rejection" + }, + { + "index": 15, + "allocator_wrapper_compiled": true, + "arguments": [ + "parse", + "4096", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 35, + "sha256": "6b136de38a5178aeca7b44dac1e50c0733a0b8c9632f074348b5b33d26a98ad3" + }, + "observation": "expected CLI rejection" + }, + { + "index": 16, + "allocator_wrapper_compiled": true, + "arguments": [ + "state", + "4096", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 35, + "sha256": "6b136de38a5178aeca7b44dac1e50c0733a0b8c9632f074348b5b33d26a98ad3" + }, + "observation": "expected CLI rejection" + }, + { + "index": 17, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "0", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 19, + "sha256": "0e79f3e5b2e11fac7bceee051f9afeb8feac756fbc965b7cb91ebbe6295bee69" + }, + "observation": "expected CLI rejection" + }, + { + "index": 18, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "4096", + "2", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 22, + "sha256": "ce8f3ca360cbfea098787240245685116513a1342afc114dbeadc8d038417902" + }, + "observation": "expected CLI rejection" + }, + { + "index": 19, + "allocator_wrapper_compiled": true, + "arguments": [ + "project", + "4096", + "0", + "nan" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 18, + "sha256": "563f8b5b0949a92663873b2724a86ae829edc1540adcfdf9cb76aafc5886e115" + }, + "observation": "expected CLI rejection" + }, + { + "index": 20, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "1", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1285, + "sha256": "5a21c91de1fa4e554f899d951ed17d8baa9a1172e9f1f0e6273cfce03cbc46ed" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 1, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 21, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "1", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1319, + "sha256": "4b1aebbdbea590864ecbeb4913398cfb108cf72001717fcfc3dd75d173db072a" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 1, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 22, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "4096", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1287, + "sha256": "9853c3c785098e3754d9afe0ddd7f8acc8a32fd44e3aac8c59a220e99e65071f" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 4096, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 23, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "4096", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1326, + "sha256": "ca19601a8c26af407391e95e22d828f86fe6d4795cebfd98e80e0e24d07e4562" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 4096, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 24, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "65536", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1292, + "sha256": "dabdbe9e5f10866618ad55340f57b4d29b49c2c554fbbba9f12d610e32746f7b" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 65536, + "observe": 0, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 25, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "65536", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1327, + "sha256": "5d35d95a7790c5336ccfc1d33ac9ca975b8d45a12c7a264ab259263d2859c77d" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "project", + "chunk": 65536, + "observe": 1, + "payload_fnv1a64": "13372643718718663029", + "state_fnv1a64": "5455070173120736870", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 26, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "1", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1285, + "sha256": "46be57d0ed2a0cdce05ecb38a195c8cee6757ddb327cfe347ee4fe9f2ab70531" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 1, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 27, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "1", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1331, + "sha256": "e46223559ae97d0a1263bb6b9d2a88a7b07ab043b8d0f6d6a76495a823106901" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 1, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 28, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "16", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1297, + "sha256": "e77c476704ec7aa4fa73a820c8017932786a14f1a15af74b4463874a9dc8bece" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 16, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 29, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "16", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1336, + "sha256": "4e96a9946a143865dcfd340e143b0fd9ff336eb7f6c4d48d28a9b1474d43b2b6" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 16, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 30, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "22000", + "0", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1291, + "sha256": "21bee017f7b469d15d5384e5b9558c6e5ab1c28323043a4677f2e7a5d72740b3" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 22000, + "observe": 0, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 31, + "allocator_wrapper_compiled": false, + "arguments": [ + "interactive", + "22000", + "1", + "0.1" + ], + "status": "passed", + "exit_code": 0, + "timed_out": false, + "stdout": { + "bytes": 1323, + "sha256": "51062e40a882a4d045bd44a53ba609d9caa843f95ab960e63b0bb07cfa0eab5c" + }, + "stderr": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "observation": "fixed replay correctness verified", + "route": "interactive", + "chunk": 22000, + "observe": 1, + "payload_fnv1a64": "14158139299817394597", + "state_fnv1a64": "8292075482910706113", + "allocation_metrics": "unknown", + "receive_seconds": null, + "presented_done": null + }, + { + "index": 32, + "allocator_wrapper_compiled": false, + "arguments": [ + "calibrate", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 71, + "sha256": "375f5c6850053495ecbecb0f75d62c77ee138901411caea56c78d41522806ed8" + }, + "observation": "expected CLI rejection" + }, + { + "index": 33, + "allocator_wrapper_compiled": false, + "arguments": [ + "parse", + "4096", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 35, + "sha256": "6b136de38a5178aeca7b44dac1e50c0733a0b8c9632f074348b5b33d26a98ad3" + }, + "observation": "expected CLI rejection" + }, + { + "index": 34, + "allocator_wrapper_compiled": false, + "arguments": [ + "state", + "4096", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 35, + "sha256": "6b136de38a5178aeca7b44dac1e50c0733a0b8c9632f074348b5b33d26a98ad3" + }, + "observation": "expected CLI rejection" + }, + { + "index": 35, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "0", + "0", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 19, + "sha256": "0e79f3e5b2e11fac7bceee051f9afeb8feac756fbc965b7cb91ebbe6295bee69" + }, + "observation": "expected CLI rejection" + }, + { + "index": 36, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "4096", + "2", + ".1" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 22, + "sha256": "ce8f3ca360cbfea098787240245685116513a1342afc114dbeadc8d038417902" + }, + "observation": "expected CLI rejection" + }, + { + "index": 37, + "allocator_wrapper_compiled": false, + "arguments": [ + "project", + "4096", + "0", + "nan" + ], + "status": "passed", + "exit_code": 2, + "timed_out": false, + "stdout": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "stderr": { + "bytes": 18, + "sha256": "563f8b5b0949a92663873b2724a86ae829edc1540adcfdf9cb76aafc5886e115" + }, + "observation": "expected CLI rejection" + } + ], + "receive_seconds": null, + "presented_done": null, + "limitations": [ + "shared host; correctness-only short sanity, no speed comparison", + "no Qt event loop, fixed ConPTY, transport, input latency or presentation", + "no formal batches, budget or WindTerm performance acceptance", + "raw intervals and process/build logs remain local; only identities and correctness projections published" + ] +} diff --git a/experiments/measure/test_recovery_measure.py b/experiments/measure/test_recovery_measure.py new file mode 100644 index 00000000..4f83dc5a --- /dev/null +++ b/experiments/measure/test_recovery_measure.py @@ -0,0 +1,140 @@ +"""Pure public-protocol tests; importing this module never launches a probe.""" +import json +from pathlib import Path +import subprocess +import tempfile +from types import SimpleNamespace +import unittest +from unittest.mock import patch + +from experiments.measure.recovery import decode_response, validate_identity, validate_sample +from experiments.measure.recovery_sanity import run_sanity + + +def identity(wrapper=True): + return { + "protocol": "termforge-recovery-core-measure-v1", "kind": "identity", + "parse_mode": "recovery", "rows": 24, "columns": 80, "history_limit": 100, + "allocator_wrapper_compiled": wrapper, + "routes": ["project", "interactive"], + "observe_0": "feed without per-round projection", + "observe_1": "feed with per-round projection and hash", + "scope": "core replay only", + "recovery_probe_passed": True, + } + + +class IdentityContract(unittest.TestCase): + def test_recovery_build_identity_must_match_the_requested_wrapper(self): + for wrapper in (False, True): + validate_identity(identity(wrapper), wrapper) + for field, value in (("protocol", "legacy"), ("parse_mode", "strict"), + ("rows", 25), ("columns", True), ("history_limit", 0), + ("allocator_wrapper_compiled", 1), ("routes", ["project"]), + ("observe_0", "none"), ("scope", "Qt product"), + ("recovery_probe_passed", False)): + broken = identity() + broken[field] = value + with self.subTest(field=field), self.assertRaises(ValueError): + validate_identity(broken, True) + with self.assertRaises(ValueError): + validate_identity(identity(False), True) + + +def sample(wrapper=True, observe=0, route="project"): + value = {name: item for name, item in identity(wrapper).items() + if name not in ("routes", "observe_0", "observe_1")} + value.update({ + "kind": "core_cost", "valid": True, "route": route, "chunk": 4096, + "observe": observe, "cpu_floor_seconds": .1, "rounds": 8, + "iterations": 8, "interval_cpu_seconds": .25, "interval_wall_seconds": .3, + "wall_gate_polls": 1, "cpu_gate_polls": 1, "wall_gate_every_iterations": 8, + "cpu_gate_min_wall_spacing_seconds": .25, "cpu_interval_differences": 1, + "counted_parser_destructions": 7, + "bytes_per_round": 78000 if route == "project" else 22000, + "workload_id": "ascii-project-78000-v1" if route == "project" else "ascii-interactive-22000-v1", + "payload_fnv1a64": "13372643718718663029" if route == "project" else "14158139299817394597", + "state_fnv1a64": "5455070173120736870" if route == "project" else "8292075482910706113", + "observer_semantics": "feed with per-round projection and hash" if observe else "feed without per-round projection", + "oracle_scope": "warmup and final; every measured projection also hashed" if observe else "warmup and final iteration", + "ordinary_allocations": 200 if wrapper and observe else None, + "ordinary_allocated_bytes": 6400 if wrapper and observe else None, + "warmup_and_oracle_seconds": .01, "final_oracle_seconds": .01, + "cpu_clock": "GetProcessTimes kernel plus user", "cpu_representation_unit_seconds": 1e-7, + "wall_clock": "std::chrono::steady_clock", "wall_clock_is_steady": True, + "wall_representation_unit_seconds": 1e-9, + "peak_working_set_bytes": 4000000, "receive_seconds": None, "presented_done": None, + }) + return value + + +class SampleContract(unittest.TestCase): + def test_fixed_complete_sample_cannot_hide_changed_work_or_unknown_counters(self): + for route in ("project", "interactive"): + for wrapper in (False, True): + for observe in (0, 1): + validate_sample(sample(wrapper, observe, route), wrapper, route, 4096, observe, .1) + for field, value in (("parse_mode", "strict"), ("chunk", True), ("observe", True), + ("state_fnv1a64", "wrong"), ("payload_fnv1a64", "wrong"), + ("workload_id", "other"), ("bytes_per_round", 78001), + ("interval_cpu_seconds", .01), ("interval_wall_seconds", float("nan")), + ("cpu_gate_polls", 0), ("iterations", 9), + ("counted_parser_destructions", 8), ("presented_done", 0), + ("ordinary_allocations", 0), ("ordinary_allocated_bytes", 0), + ("observer_semantics", "none"), ("recovery_probe_passed", False)): + broken = sample(False) + broken[field] = value + with self.subTest(field=field), self.assertRaises(ValueError): + validate_sample(broken, False, "project", 4096, 0, .1) + for field, value in (("ordinary_allocations", None), ("ordinary_allocated_bytes", True)): + broken = sample(True, 1) + broken[field] = value + with self.subTest(field=field), self.assertRaises(ValueError): + validate_sample(broken, True, "project", 4096, 1, .1) + + def test_missing_unknown_endpoint_and_invalid_requests_are_rejected(self): + for name in ("receive_seconds", "presented_done", "ordinary_allocations"): + broken = sample(False) + del broken[name] + with self.subTest(name=name), self.assertRaises(ValueError): + validate_sample(broken, False, "project", 4096, 0, .1) + for route, chunk, observe, floor in (([], 4096, 0, .1), ("project", True, 0, .1), + ("project", 4096, False, .1), ("project", 4096, 0, float("nan"))): + with self.subTest(request=(route, chunk, observe, floor)), self.assertRaises(ValueError): + validate_sample(sample(), True, route, chunk, observe, floor) + + +class RawResponseContract(unittest.TestCase): + def test_response_decoder_never_repairs_invalid_bytes_or_ambiguous_json(self): + self.assertEqual(decode_response(b'{"parse_mode":"recovery"}'), {"parse_mode": "recovery"}) + for raw in (b'{"parse_mode":"strict","parse_mode":"recovery"}', + b'{"value":NaN}', b'{"value":Infinity}', b'{"value":"\xff"}', b'partial'): + with self.subTest(raw=raw), self.assertRaises(ValueError): + decode_response(raw) + + def test_failed_first_process_keeps_exact_raw_streams_and_stops_the_batch(self): + failures = (SimpleNamespace(returncode=0, stdout=b'\xffnot-json', stderr=b''), + subprocess.TimeoutExpired(["probe"], 90, output=b'partial\xff', stderr=b'waiting')) + for failure in failures: + with self.subTest(failure=type(failure).__name__), tempfile.TemporaryDirectory() as folder: + root = Path(folder) + wrapped, noalloc = root / "wrapped.exe", root / "noalloc.exe" + wrapped.write_bytes(b"synthetic wrapped binary") + noalloc.write_bytes(b"synthetic noalloc binary") + options = {"side_effect": failure} if isinstance(failure, Exception) else {"return_value": failure} + with patch("experiments.measure.recovery_sanity.subprocess.run", **options): + with self.assertRaises(ValueError): + run_sanity(wrapped, noalloc, root / "out") + report = json.loads((root / "out/manifest.json").read_text(encoding="utf-8")) + self.assertEqual(report["status"], "failed") + self.assertEqual(report["attempted_core_processes"], 1) + self.assertEqual(len(report["records"]), 1) + self.assertEqual((root / "out/00.stdout.bin").read_bytes(), + failure.output if isinstance(failure, Exception) else failure.stdout) + self.assertEqual((root / "out/00.stderr.bin").read_bytes(), failure.stderr) + with self.assertRaises(FileExistsError): + run_sanity(wrapped, noalloc, root / "out") + + +if __name__ == "__main__": + unittest.main()