From 7ee22978baeb0d8882c84d02b2572968b4f5264b Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:17 +0800 Subject: [PATCH 01/12] feat: execute frozen grapheme cost batches with evidence guards (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from fa47eda; 文档条目另行统一提交) --- CMakeLists.txt | 2 +- experiments/measure/R54-COST.md | 33 ++- experiments/measure/r54_batch.py | 261 ++++++++++++++++++ experiments/measure/r54_environment.py | 102 +++++++ experiments/measure/r54_inputs.py | 131 +++++++++ .../2026-10-27-r54-runner/validation.json | 163 +++++++++++ experiments/measure/test_r54_batch.py | 202 ++++++++++++++ 7 files changed, 892 insertions(+), 2 deletions(-) create mode 100644 experiments/measure/r54_batch.py create mode 100644 experiments/measure/r54_environment.py create mode 100644 experiments/measure/r54_inputs.py create mode 100644 experiments/measure/results/2026-10-27-r54-runner/validation.json create mode 100644 experiments/measure/test_r54_batch.py diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7f0a5c..02dd47c1 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 -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/experiments/measure/R54-COST.md b/experiments/measure/R54-COST.md index f4a16c0b..15c7dd46 100644 --- a/experiments/measure/R54-COST.md +++ b/experiments/measure/R54-COST.md @@ -59,4 +59,35 @@ $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`。声明不能取代实际观测。源工作树必须已提交且干净,输出目录必须不存在。 + +```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。 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_environment.py b/experiments/measure/r54_environment.py new file mode 100644 index 00000000..559f7d0e --- /dev/null +++ b/experiments/measure/r54_environment.py @@ -0,0 +1,102 @@ +"""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 + +import psutil + +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(): + 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.""" + 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 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_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/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..b43048d3 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-runner/validation.json @@ -0,0 +1,163 @@ +{ + "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 + } +} diff --git a/experiments/measure/test_r54_batch.py b/experiments/measure/test_r54_batch.py new file mode 100644 index 00000000..26e55411 --- /dev/null +++ b/experiments/measure/test_r54_batch.py @@ -0,0 +1,202 @@ +"""Synthetic process/artifact contracts; these tests do not measure core speed.""" +import copy +import json +import os +from pathlib import Path +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_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() From 7dc1f0b9fd5d0914d2de2d9e6ce8b639c700b320 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:18 +0800 Subject: [PATCH 02/12] fix: isolate host sampler dependency from pure cost statistics (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from a7b9685; 文档条目另行统一提交) --- experiments/measure/R54-COST.md | 2 ++ experiments/measure/r54_environment.py | 5 +-- .../2026-10-27-r54-runner/validation.json | 34 +++++++++++++++++++ experiments/measure/test_r54_batch.py | 9 +++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/experiments/measure/R54-COST.md b/experiments/measure/R54-COST.md index 15c7dd46..fe49fa22 100644 --- a/experiments/measure/R54-COST.md +++ b/experiments/measure/R54-COST.md @@ -74,6 +74,8 @@ python -m unittest experiments.measure.test_r54_contract.OptionalAllocatorBuildS 环境声明 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 # 第二批另行启动,使用原第二种子和新目录;第一批失败时先保留并调查原因。 diff --git a/experiments/measure/r54_environment.py b/experiments/measure/r54_environment.py index 559f7d0e..49802986 100644 --- a/experiments/measure/r54_environment.py +++ b/experiments/measure/r54_environment.py @@ -6,8 +6,6 @@ import platform import time -import psutil - from experiments.measure.r54_batch import capture, utc, write_json POLICY = dict(schema='termforge-r54-environment-guard-v1', idle_intervals=10, @@ -18,6 +16,7 @@ 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() @@ -47,6 +46,7 @@ def qualify_idle(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) @@ -57,6 +57,7 @@ def process_snapshot(): def host_identity(): + import psutil import winreg def registry(key, names): with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key) as handle: diff --git a/experiments/measure/results/2026-10-27-r54-runner/validation.json b/experiments/measure/results/2026-10-27-r54-runner/validation.json index b43048d3..869bd4a8 100644 --- a/experiments/measure/results/2026-10-27-r54-runner/validation.json +++ b/experiments/measure/results/2026-10-27-r54-runner/validation.json @@ -159,5 +159,39 @@ "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 index 26e55411..d5918f7a 100644 --- a/experiments/measure/test_r54_batch.py +++ b/experiments/measure/test_r54_batch.py @@ -3,6 +3,7 @@ import json import os from pathlib import Path +import subprocess import sys import tempfile import unittest @@ -144,6 +145,14 @@ def test_complete_real_subprocess_sequence_preserves_all_pairs(self): 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 From b49fe3057af0afb8de326d5a630966bb18346107 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:19 +0800 Subject: [PATCH 03/12] perf: run frozen R54 costs in an audited hosted Windows job (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from f734d29; 文档条目另行统一提交) --- .github/workflows/r54-cost.yml | 53 +++++++ CMakeLists.txt | 2 +- experiments/measure/R54-COST.md | 2 + experiments/measure/R54-HOSTED.md | 36 +++++ experiments/measure/r54_build.py | 196 +++++++++++++++++++++++++ experiments/measure/r54_hosted.py | 70 +++++++++ experiments/measure/r54_publish.py | 148 +++++++++++++++++++ experiments/measure/test_r54_hosted.py | 87 +++++++++++ 8 files changed, 593 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/r54-cost.yml create mode 100644 experiments/measure/R54-HOSTED.md create mode 100644 experiments/measure/r54_build.py create mode 100644 experiments/measure/r54_hosted.py create mode 100644 experiments/measure/r54_publish.py create mode 100644 experiments/measure/test_r54_hosted.py diff --git a/.github/workflows/r54-cost.yml b/.github/workflows/r54-cost.yml new file mode 100644 index 00000000..023fdf31 --- /dev/null +++ b/.github/workflows/r54-cost.yml @@ -0,0 +1,53 @@ +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: + 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/CMakeLists.txt b/CMakeLists.txt index 02dd47c1..3ec7257b 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 experiments.measure.test_r54_batch -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 -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/experiments/measure/R54-COST.md b/experiments/measure/R54-COST.md index fe49fa22..d9abf9f2 100644 --- a/experiments/measure/R54-COST.md +++ b/experiments/measure/R54-COST.md @@ -93,3 +93,5 @@ Windows 1–64 逻辑 CPU 上使用 [GetSystemTimes](https://learn.microsoft.com 全部 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-HOSTED.md b/experiments/measure/R54-HOSTED.md new file mode 100644 index 00000000..90b7fd72 --- /dev/null +++ b/experiments/measure/R54-HOSTED.md @@ -0,0 +1,36 @@ +# 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` 的执行相关源码变化时启动,不在 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和临时路径前缀改为别名。没有读取完整进程命令行或任意环境变量的步骤。 + +两个核心的成本及短正确性 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、已提交的干净源码及显式任务缓存;本轮不重复已有主工作站成本预检。测试只验证既有构建输入/产物边界,正式成本样本来自上述实际作业。 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_hosted.py b/experiments/measure/r54_hosted.py new file mode 100644 index 00000000..a59cb725 --- /dev/null +++ b/experiments/measure/r54_hosted.py @@ -0,0 +1,70 @@ +"""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 + + +def hosted_identity(): + 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') + require(identity['GITHUB_REF'] == 'refs/heads/perf/grapheme-hosted-r54', '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_publish.py b/experiments/measure/r54_publish.py new file mode 100644 index 00000000..c239b493 --- /dev/null +++ b/experiments/measure/r54_publish.py @@ -0,0 +1,148 @@ +"""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 hosted_identity +from experiments.measure.r54_inputs import require + + +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') + 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): + value = json.loads((root / 'hosted.json').read_text(encoding='utf-8')) + require(value['schema'] == 'termforge-r54-hosted-v1' and value['hosted_identity'] == identity and + value['source'] == identity['GITHUB_SHA'], 'raw evidence belongs to another run, attempt or source') + + +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 + data = original + for before, after in replacements: + data = data.replace(before.encode('utf-8'), after.encode('utf-8')) + return data, status + + +def selected_paths(root): + """No recursive directory export: only named builder and runner artifacts.""" + 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')] + 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')] + return [Path(name) for name in names if (root / name).is_file()] + + +def publish(root, output): + identity = hosted_identity() + 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) + 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}')] + replacements = [] + for prefix, replacement in prefixes: + if prefix: + replacements += [(form, replacement) for form in (prefix.replace('\\', '\\\\'), prefix, prefix.replace('\\', '/'))] + + def text(value): + for before, after in replacements: + value = value.replace(before, after) + return value + + def transform(value): + if isinstance(value, dict): + return {key: transform(item) for key, item in value.items()} + 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): + 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))/(?: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 = original + for before, after in replacements: + data = data.replace(before.encode('utf-8'), after.encode('utf-8')) + 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='termforge-r54-hosted-publication-v1', + 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) + args = parser.parse_args() + print(json.dumps(publish(args.raw, args.out))) + + +if __name__ == '__main__': + main() diff --git a/experiments/measure/test_r54_hosted.py b/experiments/measure/test_r54_hosted.py new file mode 100644 index 00000000..28782dd6 --- /dev/null +++ b/experiments/measure/test_r54_hosted.py @@ -0,0 +1,87 @@ +"""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_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() From 8c22f8060f46df52fc1fc661f143795099300ee6 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:20 +0800 Subject: [PATCH 04/12] fix: preserve reviewed hosted evidence and redact short Windows paths (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from ed0c125; 文档条目另行统一提交) --- .github/workflows/r54-cost.yml | 2 + experiments/measure/R54-HOSTED.md | 6 +- experiments/measure/r54_publish.py | 52 ++- .../results/2026-10-27-r54-hosted/README.md | 24 ++ .../2026-10-27-r54-hosted/evidence.zip | Bin 0 -> 164608 bytes .../2026-10-27-r54-hosted/validation.json | 316 ++++++++++++++++++ .../results/2026-10-27-r54-hosted/verify.py | 78 +++++ experiments/measure/test_r54_hosted.py | 9 + 8 files changed, 472 insertions(+), 15 deletions(-) create mode 100644 experiments/measure/results/2026-10-27-r54-hosted/README.md create mode 100644 experiments/measure/results/2026-10-27-r54-hosted/evidence.zip create mode 100644 experiments/measure/results/2026-10-27-r54-hosted/validation.json create mode 100644 experiments/measure/results/2026-10-27-r54-hosted/verify.py diff --git a/.github/workflows/r54-cost.yml b/.github/workflows/r54-cost.yml index 023fdf31..e84887ef 100644 --- a/.github/workflows/r54-cost.yml +++ b/.github/workflows/r54-cost.yml @@ -18,6 +18,8 @@ concurrency: 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: diff --git a/experiments/measure/R54-HOSTED.md b/experiments/measure/R54-HOSTED.md index 90b7fd72..ee25c865 100644 --- a/experiments/measure/R54-HOSTED.md +++ b/experiments/measure/R54-HOSTED.md @@ -4,7 +4,7 @@ ## 运行与停止 -[工作流](../../.github/workflows/r54-cost.yml)只在实验分支 `perf/grapheme-hosted-r54` 的执行相关源码变化时启动,不在 PR 事件上重复触发,也不改变 main 的默认构建。每次工作流用独立 run ID、attempt、源码和输出目录;拒绝对旧 workflow run 的 attempt 2 及后续重跑。新的源码修订保留旧失败,并构成另一个明确身份,不替代已取得的样本。 +[工作流](../../.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`。 @@ -22,6 +22,8 @@ GitHub runner服务、操作系统后台任务、虚拟机邻居、物理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 的成本结论。 @@ -34,3 +36,5 @@ python -m experiments.measure.r54_publish --raw local/r54-raw --out local/r54-pu ``` 本机不得伪造GitHub环境变量运行该流程。普通本地准备可使用 `r54_build --out <新目录>`,要求实际Windows、已提交的干净源码及显式任务缓存;本轮不重复已有主工作站成本预检。测试只验证既有构建输入/产物边界,正式成本样本来自上述实际作业。 + +首次真实作业的来源、全部原始产物、环境护栏停止位置和独立复核见[固定记录](results/2026-10-27-r54-hosted/README.md)。这是不完整批次,没有配对成本摘要或性能通过结论。 diff --git a/experiments/measure/r54_publish.py b/experiments/measure/r54_publish.py index c239b493..5a9787a0 100644 --- a/experiments/measure/r54_publish.py +++ b/experiments/measure/r54_publish.py @@ -28,6 +28,38 @@ def validate_raw_identity(root, identity): 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 @@ -39,10 +71,7 @@ def publish_jsonl(original, replacements): except (UnicodeDecodeError, json.JSONDecodeError) as error: status.update(complete=False, error=f'{type(error).__name__}: {error}', error_line=index) break - data = original - for before, after in replacements: - data = data.replace(before.encode('utf-8'), after.encode('utf-8')) - return data, status + return redact_paths(original, replacements), status def selected_paths(root): @@ -78,15 +107,12 @@ def publish(root, output): staging.mkdir(parents=True, exist_ok=False) prefixes = [(str(ROOT), '${WORKSPACE}'), (os.environ.get('USERPROFILE', ''), '${PROFILE}'), (os.environ.get('RUNNER_TEMP', ''), '${RUNNER_TEMP}')] - replacements = [] - for prefix, replacement in prefixes: - if prefix: - replacements += [(form, replacement) for form in (prefix.replace('\\', '\\\\'), prefix, prefix.replace('\\', '/'))] + 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): - for before, after in replacements: - value = value.replace(before, after) - return value + return redact_paths(value.encode('utf-8'), replacements).decode('utf-8') def transform(value): if isinstance(value, dict): @@ -117,9 +143,7 @@ def transform(value): data, jsonl_status = publish_jsonl(original, replacements) treatment = 'jsonl_path_redaction' else: - data = original - for before, after in replacements: - data = data.replace(before.encode('utf-8'), after.encode('utf-8')) + data = redact_paths(original, replacements) treatment = 'complete_log_with_path_prefix_redaction' target = staging / relative target.parent.mkdir(parents=True, exist_ok=True) 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 0000000000000000000000000000000000000000..3f12caf74b37410a5974f2407e1a1ac3827e0d51 GIT binary patch literal 164608 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.a)@ihr8h^O44f3wjUSSiTbaZdmRZ_UA8Q^a0C$W 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]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 literal 0 HcmV?d00001 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/test_r54_hosted.py b/experiments/measure/test_r54_hosted.py index 28782dd6..a66be4d7 100644 --- a/experiments/measure/test_r54_hosted.py +++ b/experiments/measure/test_r54_hosted.py @@ -28,6 +28,15 @@ def test_unreviewed_compiler_override_stops_before_source_checkout(self): 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":[' From daaa7eee6878e3e0ebf0fe25f10a9765747a2e37 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:21 +0800 Subject: [PATCH 05/12] perf: retain whole-child CPU boundary diagnosis for #54 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from c76bab0; 文档条目另行统一提交) --- CMakeLists.txt | 2 +- experiments/measure/R54-CPU-DIAGNOSTIC.md | 37 ++ experiments/measure/r54_cpu_diagnostic.py | 236 ++++++++++ .../2026-10-27-r54-cpu-boundary/README.md | 34 ++ .../2026-10-27-r54-cpu-boundary/evidence.zip | Bin 0 -> 100053 bytes .../validation.json | 422 ++++++++++++++++++ .../2026-10-27-r54-cpu-boundary/verify.py | 73 +++ .../measure/test_r54_cpu_diagnostic.py | 98 ++++ 8 files changed, 901 insertions(+), 1 deletion(-) create mode 100644 experiments/measure/R54-CPU-DIAGNOSTIC.md create mode 100644 experiments/measure/r54_cpu_diagnostic.py create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-boundary/evidence.zip create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-boundary/verify.py create mode 100644 experiments/measure/test_r54_cpu_diagnostic.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ec7257b..772243f1 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 experiments.measure.test_r54_batch experiments.measure.test_r54_hosted -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 -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/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_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/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..34a93f29 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md @@ -0,0 +1,34 @@ +# 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)。实际执行代码与原测量源码哈希保持一致,没有重跑六位置诊断。 + +[归档](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 0000000000000000000000000000000000000000..0414e68ae633192a4f31cc401099e20274245058 GIT binary patch literal 100053 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 SV+Q>df*y?Gd4eu`!2TbVp(JGh literal 0 HcmV?d00001 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..8ad4b4e7 --- /dev/null +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json @@ -0,0 +1,422 @@ +{ + "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." + ] +} 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/test_r54_cpu_diagnostic.py b/experiments/measure/test_r54_cpu_diagnostic.py new file mode 100644 index 00000000..d97220ac --- /dev/null +++ b/experiments/measure/test_r54_cpu_diagnostic.py @@ -0,0 +1,98 @@ +"""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 + import psutil + 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() From 428321ad8ba689066bb8df9573e05d6310ecd1cc Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:21 +0800 Subject: [PATCH 06/12] ci: install pinned CPU observer for candidate contracts (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from beeb644; 文档条目另行统一提交) --- .github/workflows/core-candidate.yml | 2 ++ .../2026-10-27-r54-cpu-boundary/README.md | 2 ++ .../validation.json | 33 ++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) 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/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md b/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md index 34a93f29..338d62ab 100644 --- a/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/README.md @@ -23,6 +23,8 @@ 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、进程清单、任意环境变量或私有文件;完整本地原件保留。 离线复核命令,不执行任何测量: 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 index 8ad4b4e7..20b89e3e 100644 --- a/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json +++ b/experiments/measure/results/2026-10-27-r54-cpu-boundary/validation.json @@ -418,5 +418,36 @@ "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." + } } From 87fff2ca51f35438bdf105e828b510c8e237f8ee Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:22 +0800 Subject: [PATCH 07/12] perf: run one owned hosted CPU-boundary diagnosis for #54 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from eed0ab7; 文档条目另行统一提交) --- .github/workflows/r54-cpu.yml | 53 +++++++++++ CMakeLists.txt | 2 +- experiments/measure/R54-CPU-HOSTED.md | 21 +++++ experiments/measure/r54_cpu_hosted.py | 58 ++++++++++++ experiments/measure/r54_hosted.py | 4 +- experiments/measure/r54_publish.py | 58 ++++++++---- experiments/measure/test_r54_cpu_hosted.py | 101 +++++++++++++++++++++ 7 files changed, 275 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/r54-cpu.yml create mode 100644 experiments/measure/R54-CPU-HOSTED.md create mode 100644 experiments/measure/r54_cpu_hosted.py create mode 100644 experiments/measure/test_r54_cpu_hosted.py diff --git a/.github/workflows/r54-cpu.yml b/.github/workflows/r54-cpu.yml new file mode 100644 index 00000000..3409e31c --- /dev/null +++ b/.github/workflows/r54-cpu.yml @@ -0,0 +1,53 @@ +name: Bounded R54 hosted CPU diagnosis + +on: + push: + branches: ['perf/grapheme-cpu-hosted-r54'] + paths: + - '.github/workflows/r54-cpu.yml' + - 'experiments/measure/*.py' + +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 772243f1..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 experiments.measure.test_r54_batch experiments.measure.test_r54_hosted experiments.measure.test_r54_cpu_diagnostic -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/experiments/measure/R54-CPU-HOSTED.md b/experiments/measure/R54-CPU-HOSTED.md new file mode 100644 index 00000000..5ae187fd --- /dev/null +++ b/experiments/measure/R54-CPU-HOSTED.md @@ -0,0 +1,21 @@ +# 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),仅在分支`perf/grapheme-cpu-hosted-r54`首次创建push执行;拒绝attempt2及后续重跑。后续代码/导出器/证据修正不自动触发替代观察。原 `r54-cost.yml`、两批各112位置、原5%护栏及其失败记录不变。 + +一个新的`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_cpu_hosted.py b/experiments/measure/r54_cpu_hosted.py new file mode 100644 index 00000000..cda0cbac --- /dev/null +++ b/experiments/measure/r54_cpu_hosted.py @@ -0,0 +1,58 @@ +"""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 hosted_identity +from experiments.measure.r54_inputs import git, require + +BRANCH = 'perf/grapheme-cpu-hosted-r54' + + +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(BRANCH) + 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_hosted.py b/experiments/measure/r54_hosted.py index a59cb725..426d2f81 100644 --- a/experiments/measure/r54_hosted.py +++ b/experiments/measure/r54_hosted.py @@ -10,7 +10,7 @@ from experiments.measure.r54_inputs import git, require -def hosted_identity(): +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') @@ -19,7 +19,7 @@ def hosted_identity(): 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') - require(identity['GITHUB_REF'] == 'refs/heads/perf/grapheme-hosted-r54', 'unexpected experiment branch') + require(identity['GITHUB_REF'] == 'refs/heads/' + branch, '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 diff --git a/experiments/measure/r54_publish.py b/experiments/measure/r54_publish.py index 5a9787a0..e3c2efd3 100644 --- a/experiments/measure/r54_publish.py +++ b/experiments/measure/r54_publish.py @@ -11,10 +11,17 @@ from experiments.measure.r54_hosted import 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='perf/grapheme-cpu-hosted-r54', 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') + 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'] @@ -22,9 +29,9 @@ def public_environment(value): return result -def validate_raw_identity(root, identity): +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'] == 'termforge-r54-hosted-v1' and value['hosted_identity'] == identity and + require(value['schema'] == schema and value['hosted_identity'] == identity and value['source'] == identity['GITHUB_SHA'], 'raw evidence belongs to another run, attempt or source') @@ -74,8 +81,9 @@ def publish_jsonl(original, replacements): return redact_paths(original, replacements), status -def selected_paths(root): +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'): @@ -84,24 +92,35 @@ def selected_paths(root): 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')] - 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'): + 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(112): - names += [prefix + f'samples/{sequence:03d}/' + 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): - identity = hosted_identity() +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) + 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) @@ -116,13 +135,13 @@ def text(value): def transform(value): if isinstance(value, dict): - return {key: transform(item) for key, item in value.items()} + 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): + 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() @@ -130,7 +149,7 @@ def transform(value): # 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))/(?:stdout|stderr)\.bin', relative.as_posix())) + 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': @@ -152,7 +171,7 @@ def transform(value): 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='termforge-r54-hosted-publication-v1', + 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')) @@ -164,8 +183,9 @@ 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))) + print(json.dumps(publish(args.raw, args.out, args.experiment))) if __name__ == '__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..e799167b --- /dev/null +++ b/experiments/measure/test_r54_cpu_hosted.py @@ -0,0 +1,101 @@ +"""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) + 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) + 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) + 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() From db8556acd8227ade53470b26e81e7c4f4c7a5804 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:22 +0800 Subject: [PATCH 08/12] fix: canonicalize hosted fixtures and declare isolated setup-corrected diagnosis (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from c14adb9; 文档条目另行统一提交) --- .github/workflows/r54-cpu.yml | 2 +- experiments/measure/R54-CPU-HOSTED.md | 6 +++++- experiments/measure/r54_cpu_hosted.py | 7 ++----- experiments/measure/r54_hosted.py | 5 ++++- experiments/measure/r54_publish.py | 4 ++-- experiments/measure/test_r54_cpu_hosted.py | 8 ++++++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/workflows/r54-cpu.yml b/.github/workflows/r54-cpu.yml index 3409e31c..e56eeb55 100644 --- a/.github/workflows/r54-cpu.yml +++ b/.github/workflows/r54-cpu.yml @@ -2,7 +2,7 @@ name: Bounded R54 hosted CPU diagnosis on: push: - branches: ['perf/grapheme-cpu-hosted-r54'] + branches: ['perf/grapheme-cpu-hosted-r54', 'perf/grapheme-cpu-hosted-r54-setup2'] paths: - '.github/workflows/r54-cpu.yml' - 'experiments/measure/*.py' diff --git a/experiments/measure/R54-CPU-HOSTED.md b/experiments/measure/R54-CPU-HOSTED.md index 5ae187fd..1d7f3443 100644 --- a/experiments/measure/R54-CPU-HOSTED.md +++ b/experiments/measure/R54-CPU-HOSTED.md @@ -4,7 +4,11 @@ ## 固定执行 -以 PR #106 的 `beeb64496b2e2898e8619fa5e9c39f490a3891c4` 为基点。新入口为[工作流](../../.github/workflows/r54-cpu.yml),仅在分支`perf/grapheme-cpu-hosted-r54`首次创建push执行;拒绝attempt2及后续重跑。后续代码/导出器/证据修正不自动触发替代观察。原 `r54-cost.yml`、两批各112位置、原5%护栏及其失败记录不变。 +以 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`执行分支在创建时完成一次相同构建/六位置诊断,使用新source/run身份。两分支按完整身份区分,第二入口不覆盖首次准备失败,不增加或替换任何已存在的测量样本。该入口仍拒绝attempt2,修订push不会自动重测。 一个新的`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二进制身份。 diff --git a/experiments/measure/r54_cpu_hosted.py b/experiments/measure/r54_cpu_hosted.py index cda0cbac..f523fad5 100644 --- a/experiments/measure/r54_cpu_hosted.py +++ b/experiments/measure/r54_cpu_hosted.py @@ -7,12 +7,9 @@ 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 hosted_identity +from experiments.measure.r54_hosted import CPU_BOUNDARY_BRANCHES, hosted_identity from experiments.measure.r54_inputs import git, require -BRANCH = 'perf/grapheme-cpu-hosted-r54' - - def execute(output): output = Path(output).resolve() require(output.is_relative_to(ROOT / 'local'), 'hosted artifacts must stay under the workspace local directory') @@ -22,7 +19,7 @@ def execute(output): workflow_sha256=sha(ROOT / '.github/workflows/r54-cpu.yml'), performance_gate='not_evaluated', diagnostic=None) try: - manifest['hosted_identity'] = hosted_identity(BRANCH) + 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, diff --git a/experiments/measure/r54_hosted.py b/experiments/measure/r54_hosted.py index 426d2f81..2a14f0d6 100644 --- a/experiments/measure/r54_hosted.py +++ b/experiments/measure/r54_hosted.py @@ -9,6 +9,8 @@ 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') + def hosted_identity(branch='perf/grapheme-hosted-r54'): names = ('GITHUB_ACTIONS', 'RUNNER_ENVIRONMENT', 'RUNNER_OS', 'GITHUB_REPOSITORY', @@ -19,7 +21,8 @@ def hosted_identity(branch='perf/grapheme-hosted-r54'): 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') - require(identity['GITHUB_REF'] == 'refs/heads/' + branch, 'unexpected experiment branch') + 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 diff --git a/experiments/measure/r54_publish.py b/experiments/measure/r54_publish.py index e3c2efd3..426fb9e0 100644 --- a/experiments/measure/r54_publish.py +++ b/experiments/measure/r54_publish.py @@ -8,13 +8,13 @@ from experiments.measure.r54_batch import sha, write_json from experiments.measure.r54_contract import ROOT, SEEDS -from experiments.measure.r54_hosted import hosted_identity +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='perf/grapheme-cpu-hosted-r54', raw_schema='termforge-r54-cpu-hosted-v1', + 'cpu-boundary': dict(branch=CPU_BOUNDARY_BRANCHES, raw_schema='termforge-r54-cpu-hosted-v1', publication_schema='termforge-r54-cpu-hosted-publication-v1'), } diff --git a/experiments/measure/test_r54_cpu_hosted.py b/experiments/measure/test_r54_cpu_hosted.py index e799167b..6b6e6e02 100644 --- a/experiments/measure/test_r54_cpu_hosted.py +++ b/experiments/measure/test_r54_cpu_hosted.py @@ -14,7 +14,7 @@ def test_wrong_experiment_or_stale_run_is_rejected_before_publication_staging(se 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) + root = Path(folder).resolve() raw = root / 'local/raw' raw.mkdir(parents=True) output = root / 'local/public' @@ -52,6 +52,10 @@ def test_branch_attempt_and_source_cannot_be_mixed(self): 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) + with patch.dict(os.environ, GITHUB_REF='refs/heads/perf/grapheme-cpu-hosted-r54-setup2'): + 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)): @@ -62,7 +66,7 @@ def test_publication_keeps_failed_streams_and_separates_the_six_observation_scop 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) + 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', From d6c9651744d4ec491b2e4c77f9372963bb8e8169 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:23 +0800 Subject: [PATCH 09/12] fix: trigger the predeclared CPU diagnosis independently of push file diffs (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from 4772f4f; 文档条目另行统一提交) --- .github/workflows/r54-cpu.yml | 5 +---- experiments/measure/R54-CPU-HOSTED.md | 8 ++++++-- experiments/measure/r54_hosted.py | 3 ++- experiments/measure/test_r54_cpu_hosted.py | 9 +++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/r54-cpu.yml b/.github/workflows/r54-cpu.yml index e56eeb55..1b398c4b 100644 --- a/.github/workflows/r54-cpu.yml +++ b/.github/workflows/r54-cpu.yml @@ -2,10 +2,7 @@ name: Bounded R54 hosted CPU diagnosis on: push: - branches: ['perf/grapheme-cpu-hosted-r54', 'perf/grapheme-cpu-hosted-r54-setup2'] - paths: - - '.github/workflows/r54-cpu.yml' - - 'experiments/measure/*.py' + branches: ['perf/grapheme-cpu-hosted-r54', 'perf/grapheme-cpu-hosted-r54-setup2', 'perf/grapheme-cpu-hosted-r54-entry3'] permissions: contents: read diff --git a/experiments/measure/R54-CPU-HOSTED.md b/experiments/measure/R54-CPU-HOSTED.md index 1d7f3443..ca98eeaa 100644 --- a/experiments/measure/R54-CPU-HOSTED.md +++ b/experiments/measure/R54-CPU-HOSTED.md @@ -4,11 +4,15 @@ ## 固定执行 -以 PR #106 的 `beeb64496b2e2898e8619fa5e9c39f490a3891c4` 为基点。入口为[工作流](../../.github/workflows/r54-cpu.yml),只接受下述两个明确分支的创建push,拒绝attempt2;后续代码/导出器/证据修正不自动触发替代观察。原 `r54-cost.yml`、两批各112位置、原5%护栏及其失败记录不变。 +以 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`执行分支在创建时完成一次相同构建/六位置诊断,使用新source/run身份。两分支按完整身份区分,第二入口不覆盖首次准备失败,不增加或替换任何已存在的测量样本。该入口仍拒绝attempt2,修订push不会自动重测。 +修正与审查后,`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、停止条件或产品容差。 一个新的`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二进制身份。 diff --git a/experiments/measure/r54_hosted.py b/experiments/measure/r54_hosted.py index 2a14f0d6..e30214bd 100644 --- a/experiments/measure/r54_hosted.py +++ b/experiments/measure/r54_hosted.py @@ -9,7 +9,8 @@ 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') +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'): diff --git a/experiments/measure/test_r54_cpu_hosted.py b/experiments/measure/test_r54_cpu_hosted.py index 6b6e6e02..62738e2b 100644 --- a/experiments/measure/test_r54_cpu_hosted.py +++ b/experiments/measure/test_r54_cpu_hosted.py @@ -52,10 +52,11 @@ def test_branch_attempt_and_source_cannot_be_mixed(self): 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) - with patch.dict(os.environ, GITHUB_REF='refs/heads/perf/grapheme-cpu-hosted-r54-setup2'): - self.assertEqual(hosted.hosted_identity(hosted.CPU_BOUNDARY_BRANCHES)['GITHUB_SHA'], 'a' * 40) - with self.assertRaises(ValueError): - hosted.hosted_identity() + 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)): From e36c77051a996a79dc9334fcd842bf65fe582777 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:46:24 +0800 Subject: [PATCH 10/12] docs: retain audited hosted CPU observations and all entry outcomes (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from f871fd9; 文档条目另行统一提交) --- experiments/measure/R54-CPU-HOSTED.md | 2 +- .../2026-10-27-r54-cpu-hosted/README.md | 54 + .../2026-10-27-r54-cpu-hosted/evidence.zip | Bin 0 -> 123388 bytes .../2026-10-27-r54-cpu-hosted/validation.json | 2538 +++++++++++++++++ .../2026-10-27-r54-cpu-hosted/verify.py | 177 ++ 5 files changed, 2770 insertions(+), 1 deletion(-) create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-hosted/README.md create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-hosted/evidence.zip create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-hosted/validation.json create mode 100644 experiments/measure/results/2026-10-27-r54-cpu-hosted/verify.py diff --git a/experiments/measure/R54-CPU-HOSTED.md b/experiments/measure/R54-CPU-HOSTED.md index ca98eeaa..4a63896b 100644 --- a/experiments/measure/R54-CPU-HOSTED.md +++ b/experiments/measure/R54-CPU-HOSTED.md @@ -12,7 +12,7 @@ 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、停止条件或产品容差。 +第三入口预声明为`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二进制身份。 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 0000000000000000000000000000000000000000..f1bdd091320fba887772775b84d33de5281cbcc0 GIT binary patch literal 123388 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 literal 0 HcmV?d00001 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')) From 95f8d5fc7f8f3a2fd2ecae6f3c883bf0c9edc7ed Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:47:04 +0800 Subject: [PATCH 11/12] =?UTF-8?q?#54:=20=E6=B1=87=E6=80=BB=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=9D=A1=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把重放后的 10 个提交各自对 EVIDENCE.md / FEATURES.md 的增量合并为一次提交: - EVIDENCE.md 新增 4 行(批次协议与执行、首次 hosted 批次、CPU 计量边界诊断、新 hosted CPU 分量) - FEATURES.md 的「吞吐、响应、内存与稳定性」行更新为最终累积口径 - 补齐文件末尾换行 重放过程中文档冲突一律取 main 版,因此本提交是这些改动的唯一落点。 --- docs/EVIDENCE.md | 6 +++++- docs/FEATURES.md | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) 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 分平台出口 | From c05fd4d5fa63993e545115ada1f4355eb2c35f6a Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:54:39 +0800 Subject: [PATCH 12/12] =?UTF-8?q?fix(measure):=20=E7=BC=BA=E5=B0=91=20psut?= =?UTF-8?q?il=20=E6=97=B6=E8=B7=B3=E8=BF=87=20CPU=20=E8=AF=8A=E6=96=AD?= =?UTF-8?q?=E8=80=8C=E9=9D=9E=E5=AF=BC=E5=85=A5=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重放到 main 后,通用 Windows 回归作业也会执行 measure 这个 CTest 项,而该作业不安装 experiments/measure 的依赖。native_environment_supported() 在 Windows 上无条件 import psutil,@unittest.skipUnless 的参数又在导入期求值,于是缺依赖变成 ImportError, 整个 measure 项失败。 把 import 包进 try/except 并返回 False:缺少观察器表示「此环境不支持」,decorator 仍然跳过, 而不是让收集阶段报错。装有 psutil 的 r54-cpu 作业行为不变。 --- experiments/measure/test_r54_cpu_diagnostic.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/experiments/measure/test_r54_cpu_diagnostic.py b/experiments/measure/test_r54_cpu_diagnostic.py index d97220ac..1a3925da 100644 --- a/experiments/measure/test_r54_cpu_diagnostic.py +++ b/experiments/measure/test_r54_cpu_diagnostic.py @@ -30,7 +30,13 @@ def test_own_interval_excluded_cpu_is_separated_and_negative_balance_is_retained def native_environment_supported(): if os.name != 'nt' or sys.implementation.name != 'cpython' or sys.version_info[:2] != (3, 12): return False - import psutil + 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