From b9ce4dcf5b742f1ccb69203fd9ee1b9627c11e99 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:39:25 +0800 Subject: [PATCH 1/5] research: build the pinned ConPTY margin candidate on a disposable host (#56) Preserve upstream toolchain/dependency identity; require a causal TAEF red, focused and full adapter green before creating an experimental pair. Runtime and product acceptance remain unverified. --- .github/workflows/conpty-upstream.yml | 67 +++++++ experiments/conpty/upstream/README.md | 24 ++- experiments/conpty/upstream/build_hosted.ps1 | 169 ++++++++++++++++++ experiments/conpty/upstream/taef_result.py | 58 ++++++ .../conpty/upstream/test_taef_result.py | 51 ++++++ 5 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/conpty-upstream.yml create mode 100644 experiments/conpty/upstream/build_hosted.ps1 create mode 100644 experiments/conpty/upstream/taef_result.py create mode 100644 experiments/conpty/upstream/test_taef_result.py diff --git a/.github/workflows/conpty-upstream.yml b/.github/workflows/conpty-upstream.yml new file mode 100644 index 00000000..d6edd78c --- /dev/null +++ b/.github/workflows/conpty-upstream.yml @@ -0,0 +1,67 @@ +name: Fixed upstream ConPTY build experiment + +on: + pull_request: + paths: + - '.github/workflows/conpty-upstream.yml' + - 'experiments/conpty/upstream/**' + workflow_dispatch: + +permissions: + contents: read + +jobs: + upstream: + runs-on: windows-2025 + timeout-minutes: 45 + env: + PYTHONUTF8: '1' + NUGET_CLI_LANGUAGE: en-us + VCPKG_DISABLE_METRICS: '1' + VCPKG_MAX_CONCURRENCY: '1' + steps: + - name: Preserve canonical source bytes in disposable checkouts + run: git config --global core.autocrlf false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: project + persist-credentials: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: microsoft/terminal + ref: dc8ae365847d41f62d1ebca93815bb00959bc12f + path: upstream + persist-credentials: false + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: microsoft/vcpkg + ref: 927f62e4b8838bd7e441e9c45103a16ffd75007e + path: vcpkg + persist-credentials: false + - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 + with: + python-version: '3.12' + - name: Test result rejection gates + working-directory: project + run: python -m unittest experiments.conpty.upstream.test_taef_result -v + - name: Build and test fixed upstream variants + shell: pwsh + run: >- + ./project/experiments/conpty/upstream/build_hosted.ps1 + -Source ./upstream -Vcpkg ./vcpkg -Out ./upstream-result + - name: Preserve source and test identity + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-summary + retention-days: 14 + if-no-files-found: warn + path: upstream-result/public/ + - name: Preserve experimental pair after adapter verification + if: success() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-experimental-pair + retention-days: 14 + if-no-files-found: error + path: upstream-result/pair/ diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index e8a6f716..fa8385fc 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -15,7 +15,10 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate 在单独的 Microsoft Terminal 源码 checkout 固定上述提交,校验 [validation.json](../results/2026-10-26-native-margin-path/validation.json) 的源文件 SHA 后,先用 `git apply --unidiff-zero --check` 检查、再以同一选项应用本 patch。完整保留上游许可证、依赖锁和构建日志。 按固定 [上游 README](https://github.com/microsoft/terminal/blob/dc8ae365847d41f62d1ebca93815bb00959bc12f/README.md#building-the-code) -准备工具链;不要执行会更改个人系统的 winget/Developer Mode 安装步骤。优先使用已有工具或隔离构建宿主。 +和实际构建文件准备工具链;固定提交的 +[`x64-windows-static.cmake`](https://github.com/microsoft/terminal/blob/dc8ae365847d41f62d1ebca93815bb00959bc12f/dep/vcpkg-overlay-triplets/x64-windows-static.cmake) +明确要求 **v145**,README 中的 VS2022 概述不能证明仅有 v143 的宿主满足依赖构建。 +不要执行会更改个人系统的 winget/Developer Mode 安装步骤。优先使用已有工具或隔离构建宿主。 从上游根目录加载 `tools/OpenConsole.psm1`、调用 `Set-MsBuildDevEnvironment` 后,逐一构建这些真实项目入口, 使用 `Configuration=Release`、`Platform=x64`、正确的根 `SolutionDir`、restore 和单 worker;每一步非零立即停止: @@ -28,6 +31,25 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate 这些入口来自同一提交的 `OpenConsole.slnx`;不是已执行或已验证的构建命令结果。 测试入口可用上游 `Invoke-OpenConsoleTests -Test adapter`,其映射在 `tools/tests.xml`,须限制为无交互 unit 路径。 +### 一次性 hosted 构建入口 + +[专用 workflow](../../../.github/workflows/conpty-upstream.yml)只在本目录/自身的 PR 改动或显式 dispatch 时运行, +独立于默认产品构建。它固定上述源码、上游 manifest 的 vcpkg baseline `927f62e4b8838bd7e441e9c45103a16ffd75007e`、 +vcpkg 工具 2026-04-08 的发布 SHA、现有 patch 与关键源码 SHA;在真正的 GitHub-hosted Windows 环境核对 v145/SDK。 +上游 NuGet 锁定版本、vcpkg overlay 和 terminal feature 均保留。宿主镜像与实际编译器版本/哈希另记,不能据 runner 名称假定工具不变。 + +[脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; +缺包、编译错误、加载错误、零测试、跳过或超时均不算 red。再仅应用两个实现文件的 patch,重建并分别执行一次 focused 和完整 adapter 测试, +通过后才从同源构建 EXE/DLL。TAEF focused/session 期限分别为60/600秒,整个构建 job 最长45分钟,没有自动重试。 +[判据](taef_result.py)和[拒绝用例](test_taef_result.py)防止把进程失败、缺失执行或局部测试当作完整结果。 +TAEF 参数语义来自 [Microsoft 文档](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/te-exe-command-line-parameters), +单失败 red 只接受退出码1;[harness/超时高位](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/exit-codes-for-taef)不得混作断言失败。 + +workflow 保存白名单 summary、实际包/工具/双文件 SHA;完整日志仅在 CI 日志中,额外文件留在不上传的 `_private`。 +测试通过后的短期实验 artifact 带上游 LICENSE/NOTICE 和补丁,不包含 TAEF、NuGet 工具或 WindTerm 文件。 +这些是可执行的验证步骤;其实际结果由对应 PR/#56 记录,入口存在不表示已经编译或通过。 +即使 adapter 通过,仍须新 fork 身份、原175B四阶段、原生/运输/候选末端及反例验证;不能加载为现有官方包或据此结项。 + 构建成功后,为**新的 fork 双文件身份**单独增加显式、默认关闭的测试配置与审查清单;保持当前官方包校验失败即拒绝。 不能把 fork 冒充官方 1.25,也不能用新的 EXE 混配另一个版本的 DLL。create/resize/close 必须来自同模块, 模块存活至 HPCON、读线程和子进程回收之后。记录来源、补丁、工具链、文件 SHA、许可和失败路径。 diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 new file mode 100644 index 00000000..d3000e5d --- /dev/null +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -0,0 +1,169 @@ +param( + [Parameter(Mandatory)][string]$Source, + [Parameter(Mandatory)][string]$Vcpkg, + [Parameter(Mandatory)][string]$Out +) +$ErrorActionPreference = 'Stop' +$PSNativeCommandUseErrorActionPreference = $false +if (-not $IsWindows -or $env:GITHUB_ACTIONS -ne 'true' -or $env:RUNNER_ENVIRONMENT -ne 'github-hosted') { + throw 'This entry requires a disposable GitHub-hosted Windows runner.' +} +$repo = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path +$Source = (Resolve-Path -LiteralPath $Source).Path +$Vcpkg = (Resolve-Path -LiteralPath $Vcpkg).Path +$Out = [IO.Path]::GetFullPath($Out, (Get-Location).Path) +$workspacePrefix = (Resolve-Path -LiteralPath $env:GITHUB_WORKSPACE).Path.TrimEnd('\') + '\' +foreach ($path in @($Source, $Vcpkg, $Out)) { + if (-not $path.StartsWith($workspacePrefix, [StringComparison]::OrdinalIgnoreCase)) { + throw 'Source, dependencies and output must be children of this disposable workspace.' + } +} +if (Test-Path -LiteralPath $Out) { throw 'A fresh output directory is required.' } +New-Item -ItemType Directory -Path "$Out/public", "$Out/_private", "$Out/cache" | Out-Null +$state = [ordered]@{ + schema = 'termforge-upstream-conpty-build-v1' + purpose = 'isolated source-build and adapter experiment; not an official ConPTY package' + status = 'running' + termforge_source = (& git -C $repo rev-parse HEAD) + upstream_source = 'dc8ae365847d41f62d1ebca93815bb00959bc12f' + vcpkg_source = '927f62e4b8838bd7e441e9c45103a16ffd75007e' + image_os = $env:ImageOS + image_version = $env:ImageVersion + workflow_run = $env:GITHUB_RUN_ID + workflow_attempt = $env:GITHUB_RUN_ATTEMPT + configuration = 'Release/x64/v145/WindowsSDK10.0.26100.0' + stages = @() + test_binaries = @() + adapter_tests_verified = $false + native_runtime_verified = $false + full_r2_contract_verified = $false + product_backend_selected = $false +} +$currentStage = 'source-identity' +function Save-State { + $state | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath "$Out/public/summary.json" -Encoding utf8 +} +function Digest([string]$Path) { + (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() +} +function Invoke-Native([string]$Name, [string]$Executable, [string[]]$Arguments, [switch]$AllowFailure) { + $script:currentStage = $Name + $clock = [Diagnostics.Stopwatch]::StartNew() + Set-Content -LiteralPath "$Out/_private/$Name.log" -Value '' -NoNewline -Encoding utf8 + & $Executable @Arguments 2>&1 | Tee-Object -FilePath "$Out/_private/$Name.log" | Out-Host + $code = $LASTEXITCODE + $state.stages += [ordered]@{name=$Name; exit_code=$code; elapsed_seconds=$clock.Elapsed.TotalSeconds; log_sha256=(Digest "$Out/_private/$Name.log")} + Save-State + if ($code -ne 0 -and -not $AllowFailure) { throw "Stage $Name failed with exit code $code." } + return $code +} +function Build-Project([string]$Name, [string]$Project) { + [void](Invoke-Native $Name 'msbuild.exe' @($Project, '/nologo', '/restore', '/m:1', '/nr:false', '/v:minimal', + '/p:Configuration=Release', '/p:Platform=x64', '/p:PlatformToolset=v145', + '/p:WindowsTargetPlatformVersion=10.0.26100.0', '/p:CL_MPCount=1', '/p:UseMultiToolTask=false', + "/p:VCToolsVersion=$env:VCToolsVersion", "/p:SolutionDir=$Source\", "/p:VcpkgRoot=$Vcpkg\")) +} +function Test-Adapter([string]$Name, [string]$Expected, [switch]$Full) { + $testBinary = "$Source/bin/x64/Release/ConAdapter.Unit.Tests.dll" + $binaryHash = Digest $testBinary + $state.test_binaries += [ordered]@{stage=$Name; test_dll_sha256=$binaryHash; + taef_sha256=(Digest "$Source/packages/Microsoft.Taef.10.100.251104001/build/Binaries/x64/te.exe")} + $testArgs = @("$Source/bin/x64/Release/ConAdapter.Unit.Tests.dll", '/labMode', + '/coloredConsoleOutput:false', '/unicodeOutput:false', '/console:flushWrites') + if ($Full) { $testArgs += '/sessionTimeout:0:10:0' } + else { $testArgs += @('/name:*AlternateScreenMarginsTest', '/sessionTimeout:0:1:0') } + $code = Invoke-Native $Name "$Source/packages/Microsoft.Taef.10.100.251104001/build/Binaries/x64/te.exe" $testArgs -AllowFailure + [void](Invoke-Native "$Name-contract" 'python' @("$PSScriptRoot/taef_result.py", "$Out/_private/$Name.log", + '--exit-code', "$code", '--expected', $Expected, '--out', "$Out/public/$Name.json")) + if ((Digest $testBinary) -ne $binaryHash) { throw 'Test binary identity changed during execution.' } +} +try { + Save-State + if ((& git -C $Source rev-parse HEAD) -ne $state.upstream_source) { throw 'Wrong Terminal source.' } + if ((& git -C $Vcpkg rev-parse HEAD) -ne $state.vcpkg_source) { throw 'Wrong vcpkg source.' } + if (@(& git -C $Source status --porcelain).Count -ne 0) { throw 'Upstream checkout is dirty.' } + $prior = Get-Content -Raw -LiteralPath "$repo/experiments/conpty/results/2026-10-26-native-margin-path/validation.json" | ConvertFrom-Json -AsHashtable + foreach ($file in $prior.upstream_source_sha256.Keys) { + if ((Digest (Join-Path $Source $file)) -ne $prior.upstream_source_sha256[$file]) { throw "Source hash differs: $file" } + } + $patch = "$PSScriptRoot/dc8-native-margin-candidate.patch" + if ((Digest $patch) -ne 'dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81') { throw 'Patch identity differs.' } + $state.patch_sha256 = Digest $patch + $state.source_hashes_verified = $true + $triplet = Get-Content -Raw -LiteralPath "$Source/dep/vcpkg-overlay-triplets/x64-windows-static.cmake" + if ($triplet -notmatch 'set\(VCPKG_PLATFORM_TOOLSET v145\)') { throw 'Unexpected upstream toolset requirement.' } + + $currentStage = 'toolchain' + $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" + $vs = & $vswhere -latest -products '*' -version '[18.0,19.0)' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if ($LASTEXITCODE -ne 0 -or -not $vs) { throw 'The pinned triplet requires an installed v145 toolchain.' } + Import-Module "$vs/Common7/Tools/Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64 -winsdk=10.0.26100.0' + if ($env:VisualStudioVersion -notlike '18.*') { throw 'Expected the v145 Visual Studio environment.' } + $state.tools = [ordered]@{visual_studio=$env:VisualStudioVersion; msvc=$env:VCToolsVersion; + sdk=$env:WindowsSDKVersion; compiler_sha256=(Digest (Get-Command cl.exe).Source); + msbuild_sha256=(Digest (Get-Command msbuild.exe).Source)} + $env:VCPKG_ROOT = $Vcpkg + $env:VCPKG_DISABLE_METRICS = '1' + $env:VCPKG_MAX_CONCURRENCY = '1' + $env:VCPKG_DOWNLOADS = "$Out/cache/vcpkg-downloads" + $env:VCPKG_DEFAULT_BINARY_CACHE = "$Out/cache/vcpkg-binary" + $env:VCPKG_BINARY_SOURCES = "clear;files,$env:VCPKG_DEFAULT_BINARY_CACHE,readwrite" + $env:NUGET_PACKAGES = "$Out/cache/nuget-global" + $env:NUGET_HTTP_CACHE_PATH = "$Out/cache/nuget-http" + $env:NUGET_PLUGINS_CACHE_PATH = "$Out/cache/nuget-plugins" + foreach ($folder in @($env:VCPKG_DOWNLOADS, $env:VCPKG_DEFAULT_BINARY_CACHE, $env:NUGET_PACKAGES, + $env:NUGET_HTTP_CACHE_PATH, $env:NUGET_PLUGINS_CACHE_PATH)) { + New-Item -ItemType Directory -Path $folder | Out-Null + } + Set-Location -LiteralPath $Source + [void](Invoke-Native 'vcpkg-tool-download' 'curl.exe' @('--fail', '--location', '--silent', '--show-error', + '--connect-timeout', '10', '--max-time', '180', '--retry', '0', '--output', "$Vcpkg/vcpkg.exe", + 'https://github.com/microsoft/vcpkg-tool/releases/download/2026-04-08/vcpkg.exe')) + $state.vcpkg_exe_sha256 = Digest "$Vcpkg/vcpkg.exe" + if ($state.vcpkg_exe_sha256 -ne 'f1edbf3a39de350e2bb065214fdc057111aa87a2e2ed9a7dcb8ddc86e17751b9') { throw 'vcpkg release digest differs.' } + [void](Invoke-Native 'vcpkg-version' "$Vcpkg/vcpkg.exe" @('version', '--disable-metrics')) + [void](Invoke-Native 'nuget-restore' "$Source/dep/nuget/nuget.exe" @('restore', "$Source/dep/nuget/packages.config", + '-ConfigFile', "$Source/NuGet.Config", '-PackagesDirectory', "$Source/packages", '-DisableParallelProcessing', '-NonInteractive')) + + $testFile = 'src/terminal/adapter/ut_adapter/adapterTest.cpp' + [void](Invoke-Native 'baseline-patch-check' 'git' @('apply', '--unidiff-zero', "--include=$testFile", '--check', $patch)) + [void](Invoke-Native 'baseline-test-patch' 'git' @('apply', '--unidiff-zero', "--include=$testFile", $patch)) + if ((@(& git diff --name-only) -join ',') -ne $testFile) { throw 'Baseline changed production source.' } + Build-Project 'baseline-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' + Test-Adapter 'baseline-regression' 'red' + + [void](Invoke-Native 'production-patch-check' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", '--check', $patch)) + [void](Invoke-Native 'production-patch' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", $patch)) + $expectedFiles = @('src/terminal/adapter/adaptDispatch.cpp', 'src/terminal/adapter/adaptDispatch.hpp', $testFile) + if ((@(& git diff --name-only) -join ',') -ne ($expectedFiles -join ',')) { throw 'Unexpected patched source files.' } + Build-Project 'patched-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' + Test-Adapter 'patched-regression' 'focused-green' + Test-Adapter 'patched-adapter' 'full-green' -Full + $state.adapter_tests_verified = $true + Build-Project 'patched-host-build' 'src/host/exe/Host.EXE.vcxproj' + Build-Project 'patched-dll-build' 'src/winconpty/dll/winconptydll.vcxproj' + + $currentStage = 'artifact-identity' + New-Item -ItemType Directory -Path "$Out/pair" | Out-Null + $state.files = [ordered]@{} + foreach ($name in @('OpenConsole.exe', 'conpty.dll')) { + $binary = "$Source/bin/x64/Release/$name" + $state.files[$name] = [ordered]@{sha256=(Digest $binary); bytes=(Get-Item -LiteralPath $binary).Length} + Copy-Item -LiteralPath $binary -Destination "$Out/pair/$name" + } + $state.nuget_packages = @() + foreach ($package in Get-ChildItem -LiteralPath "$Source/packages" -Filter '*.nupkg' -Recurse -File) { + $state.nuget_packages += [ordered]@{name=$package.Name; sha256=(Digest $package.FullName)} + } + $state.vcpkg_status_sha256 = Digest "$Source/obj/x64/vcpkg/vcpkg/status" + $state.status = 'adapter-verified-runtime-unverified' + Save-State + Copy-Item -LiteralPath "$Out/public/summary.json", $patch, "$Source/LICENSE", "$Source/NOTICE.md" -Destination "$Out/pair" +} catch { + $state.status = 'failed' + $state.failed_stage = $currentStage + throw +} finally { + Save-State +} diff --git a/experiments/conpty/upstream/taef_result.py b/experiments/conpty/upstream/taef_result.py new file mode 100644 index 00000000..61176750 --- /dev/null +++ b/experiments/conpty/upstream/taef_result.py @@ -0,0 +1,58 @@ +"""Reject missing, skipped, loading-error or unexpected TAEF results.""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import re + +SUMMARY = re.compile( + r"Summary:\s*Total=(\d+),\s*Passed=(\d+),\s*Failed=(\d+),\s*" + r"Blocked=(\d+),\s*Not Run=(\d+),\s*Skipped=(\d+)") +METHOD = "AlternateScreenMarginsTest" + + +def validate(text: str, exit_code: int, expected: str) -> dict: + rows = SUMMARY.findall(text) + if len(rows) != 1: + raise ValueError("Expected exactly one completed TAEF summary") + counts = dict(zip(("total", "passed", "failed", "blocked", "not_run", "skipped"), + map(int, rows[0]))) + if counts["total"] != sum(counts[key] for key in counts if key != "total"): + raise ValueError("Inconsistent test counts") + if any(counts[key] for key in ("blocked", "not_run", "skipped")): + raise ValueError("Blocked, unexecuted or skipped tests are not acceptance") + if METHOD not in text: + raise ValueError("The margin regression was not observed") + if expected == "red": + # TAEF's high bits report harness/timeout faults; the low bits count failures. + if exit_code != 1 or counts != dict(total=1, passed=0, failed=1, blocked=0, not_run=0, skipped=0): + raise ValueError("Expected one failed regression") + if not re.search(r"(?:Error|Failed)[^\n]*Verify[^\n]*_scrollMargins", text, re.IGNORECASE): + raise ValueError("Failure did not identify the scrolling-margin assertion") + elif expected in ("focused-green", "full-green"): + if exit_code != 0 or counts["failed"] or counts["passed"] != counts["total"]: + raise ValueError("Expected a successful TAEF execution") + if expected == "focused-green" and counts["total"] != 1: + raise ValueError("Expected exactly one focused regression") + if expected == "full-green" and counts["total"] <= 1: + raise ValueError("A focused test cannot stand in for the full adapter suite") + else: + raise ValueError("Unknown result contract") + return {"expected": expected, "exit_code": exit_code, "counts": counts, "accepted": True} + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("log", type=Path) + parser.add_argument("--exit-code", type=int, required=True) + parser.add_argument("--expected", choices=("red", "focused-green", "full-green"), required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + result = validate(args.log.read_text(encoding="utf-8-sig"), args.exit_code, args.expected) + args.out.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + print(json.dumps(result)) + + +if __name__ == "__main__": + main() diff --git a/experiments/conpty/upstream/test_taef_result.py b/experiments/conpty/upstream/test_taef_result.py new file mode 100644 index 00000000..d59fab07 --- /dev/null +++ b/experiments/conpty/upstream/test_taef_result.py @@ -0,0 +1,51 @@ +import unittest + +from experiments.conpty.upstream.taef_result import METHOD, validate + + +def output(passed=0, failed=1, blocked=0, not_run=0, skipped=0): + total = passed + failed + blocked + not_run + skipped + assertion = "Error: Verify: AreEqual(til::inclusive_rect{}, _pDispatch->_scrollMargins)\n" if failed else "" + return (f"StartGroup: Adapter::{METHOD}\n" + f"{assertion}" + f"Summary: Total={total}, Passed={passed}, Failed={failed}, " + f"Blocked={blocked}, Not Run={not_run}, Skipped={skipped}\n") + + +class TaefResultTests(unittest.TestCase): + def test_expected_assertion_failure_is_red(self): + self.assertTrue(validate(output(), 1, "red")["accepted"]) + + def test_process_failure_is_not_a_red_regression(self): + for text in ("Error loading DLL", output().replace("Error: Verify", "Loader failure:"), + output().replace(METHOD, "UnrelatedTest"), output(blocked=1, failed=0)): + with self.subTest(text=text), self.assertRaises(ValueError): + validate(text, 1, "red") + + def test_no_tests_multiple_summaries_and_skips_are_rejected(self): + for text in (output(failed=0), output() + output(), output(passed=1, failed=0, skipped=1)): + with self.subTest(text=text), self.assertRaises(ValueError): + validate(text, 0, "focused-green") + + def test_focused_and_full_success_remain_separate(self): + self.assertTrue(validate(output(passed=1, failed=0), 0, "focused-green")["accepted"]) + self.assertTrue(validate(output(passed=5, failed=0), 0, "full-green")["accepted"]) + with self.assertRaises(ValueError): + validate(output(passed=1, failed=0), 0, "full-green") + with self.assertRaises(ValueError): + validate(output(passed=5, failed=0), 0, "focused-green") + + def test_exit_code_and_counts_must_agree(self): + for text, code, contract in ((output(), 0, "red"), (output(passed=1, failed=0), 1, "focused-green"), + (output().replace("Total=1", "Total=2"), 1, "red")): + with self.subTest(contract=contract), self.assertRaises(ValueError): + validate(text, code, contract) + + def test_harness_error_bits_cannot_be_accepted_as_causal_red(self): + for code in (2, 0x08000001, 0x02000001, 0x01000001, -1): + with self.subTest(code=code), self.assertRaises(ValueError): + validate(output(), code, "red") + + +if __name__ == "__main__": + unittest.main() From 4d5ade7bf4f33e27c316185618a234ab92f788ac Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:48:15 +0800 Subject: [PATCH 2/5] fix: preserve pinned patch bytes on Windows checkout text=auto still uses native EOL with autocrlf=false; explicitly use LF in the disposable host. Preserve the failed first CI and unchanged patch SHA. --- .github/workflows/conpty-upstream.yml | 4 +++- experiments/conpty/upstream/README.md | 1 + experiments/conpty/upstream/build_hosted.ps1 | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conpty-upstream.yml b/.github/workflows/conpty-upstream.yml index d6edd78c..9026dcfc 100644 --- a/.github/workflows/conpty-upstream.yml +++ b/.github/workflows/conpty-upstream.yml @@ -21,7 +21,9 @@ jobs: VCPKG_MAX_CONCURRENCY: '1' steps: - name: Preserve canonical source bytes in disposable checkouts - run: git config --global core.autocrlf false + run: | + git config --global core.autocrlf false + git config --global core.eol lf - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: project diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index fa8385fc..a3b0365a 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -36,6 +36,7 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate [专用 workflow](../../../.github/workflows/conpty-upstream.yml)只在本目录/自身的 PR 改动或显式 dispatch 时运行, 独立于默认产品构建。它固定上述源码、上游 manifest 的 vcpkg baseline `927f62e4b8838bd7e441e9c45103a16ffd75007e`、 vcpkg 工具 2026-04-08 的发布 SHA、现有 patch 与关键源码 SHA;在真正的 GitHub-hosted Windows 环境核对 v145/SDK。 +临时宿主的 checkout 同时固定 `core.autocrlf=false` 和 `core.eol=lf`,避免 `text=auto` 在 Windows 上改变补丁字节;原 SHA 校验保持严格。 上游 NuGet 锁定版本、vcpkg overlay 和 terminal feature 均保留。宿主镜像与实际编译器版本/哈希另记,不能据 runner 名称假定工具不变。 [脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 index d3000e5d..08054c6c 100644 --- a/experiments/conpty/upstream/build_hosted.ps1 +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -87,8 +87,8 @@ try { if ((Digest (Join-Path $Source $file)) -ne $prior.upstream_source_sha256[$file]) { throw "Source hash differs: $file" } } $patch = "$PSScriptRoot/dc8-native-margin-candidate.patch" - if ((Digest $patch) -ne 'dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81') { throw 'Patch identity differs.' } $state.patch_sha256 = Digest $patch + if ($state.patch_sha256 -ne 'dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81') { throw 'Patch identity differs.' } $state.source_hashes_verified = $true $triplet = Get-Content -Raw -LiteralPath "$Source/dep/vcpkg-overlay-triplets/x64-windows-static.cmake" if ($triplet -notmatch 'set\(VCPKG_PLATFORM_TOOLSET v145\)') { throw 'Unexpected upstream toolset requirement.' } From ec66be19f42cfc022c45a52e1d138a2ad8c0a3b3 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 11:52:58 +0800 Subject: [PATCH 3/5] fix: retain historical port objects for the pinned vcpkg manifest Keep the exact baseline and dependency versions. A full Git history is required for the fixed cmark and cli11 trees missing in the shallow clone. --- .github/workflows/conpty-upstream.yml | 1 + experiments/conpty/upstream/README.md | 1 + experiments/conpty/upstream/build_hosted.ps1 | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/conpty-upstream.yml b/.github/workflows/conpty-upstream.yml index 9026dcfc..e1c64048 100644 --- a/.github/workflows/conpty-upstream.yml +++ b/.github/workflows/conpty-upstream.yml @@ -38,6 +38,7 @@ jobs: with: repository: microsoft/vcpkg ref: 927f62e4b8838bd7e441e9c45103a16ffd75007e + fetch-depth: 0 path: vcpkg persist-credentials: false - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0 diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index a3b0365a..2eecdb44 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -37,6 +37,7 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate 独立于默认产品构建。它固定上述源码、上游 manifest 的 vcpkg baseline `927f62e4b8838bd7e441e9c45103a16ffd75007e`、 vcpkg 工具 2026-04-08 的发布 SHA、现有 patch 与关键源码 SHA;在真正的 GitHub-hosted Windows 环境核对 v145/SDK。 临时宿主的 checkout 同时固定 `core.autocrlf=false` 和 `core.eol=lf`,避免 `text=auto` 在 Windows 上改变补丁字节;原 SHA 校验保持严格。 +vcpkg 使用完整 Git 历史,HEAD 仍固定在原 baseline;[历史 port 的 tree 对象不能由 shallow checkout 保证](https://learn.microsoft.com/en-us/vcpkg/users/versioning-troubleshooting)。 上游 NuGet 锁定版本、vcpkg overlay 和 terminal feature 均保留。宿主镜像与实际编译器版本/哈希另记,不能据 runner 名称假定工具不变。 [脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 index 08054c6c..3a2b555f 100644 --- a/experiments/conpty/upstream/build_hosted.ps1 +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -81,6 +81,8 @@ try { Save-State if ((& git -C $Source rev-parse HEAD) -ne $state.upstream_source) { throw 'Wrong Terminal source.' } if ((& git -C $Vcpkg rev-parse HEAD) -ne $state.vcpkg_source) { throw 'Wrong vcpkg source.' } + $state.vcpkg_shallow = (& git -C $Vcpkg rev-parse --is-shallow-repository) + if ($LASTEXITCODE -ne 0 -or $state.vcpkg_shallow -ne 'false') { throw 'Pinned port versions require the full vcpkg Git history.' } if (@(& git -C $Source status --porcelain).Count -ne 0) { throw 'Upstream checkout is dirty.' } $prior = Get-Content -Raw -LiteralPath "$repo/experiments/conpty/results/2026-10-26-native-margin-path/validation.json" | ConvertFrom-Json -AsHashtable foreach ($file in $prior.upstream_source_sha256.Keys) { From b56058e9c4becb30f6afda3100b73acf1e552ff7 Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:03:37 +0800 Subject: [PATCH 4/5] fix: honor solution prerequisites before direct upstream project builds Generate Proxy MIDL headers before hostlib, and preserve the fixed solution's Types, Buffer, Render and Propsheet build ordering without source changes. --- experiments/conpty/upstream/README.md | 1 + experiments/conpty/upstream/build_hosted.ps1 | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index 2eecdb44..b154d27a 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -39,6 +39,7 @@ vcpkg 工具 2026-04-08 的发布 SHA、现有 patch 与关键源码 SHA;在 临时宿主的 checkout 同时固定 `core.autocrlf=false` 和 `core.eol=lf`,避免 `text=auto` 在 Windows 上改变补丁字节;原 SHA 校验保持严格。 vcpkg 使用完整 Git 历史,HEAD 仍固定在原 baseline;[历史 port 的 tree 对象不能由 shallow checkout 保证](https://learn.microsoft.com/en-us/vcpkg/users/versioning-troubleshooting)。 上游 NuGet 锁定版本、vcpkg overlay 和 terminal feature 均保留。宿主镜像与实际编译器版本/哈希另记,不能据 runner 名称假定工具不变。 +单项目入口显式补齐固定 `OpenConsole.slnx` 的顺序依赖:adapter 前构建 Proxy/Types/Buffer/Render Base/GDI,生成所需接口头;Host.EXE 前构建 Propsheet。 [脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; 缺包、编译错误、加载错误、零测试、跳过或超时均不算 red。再仅应用两个实现文件的 patch,重建并分别执行一次 focused 和完整 adapter 测试, diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 index 3a2b555f..ffcbb7f6 100644 --- a/experiments/conpty/upstream/build_hosted.ps1 +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -132,6 +132,12 @@ try { [void](Invoke-Native 'baseline-patch-check' 'git' @('apply', '--unidiff-zero', "--include=$testFile", '--check', $patch)) [void](Invoke-Native 'baseline-test-patch' 'git' @('apply', '--unidiff-zero', "--include=$testFile", $patch)) if ((@(& git diff --name-only) -join ',') -ne $testFile) { throw 'Baseline changed production source.' } + # Single-project builds do not inherit OpenConsole.slnx BuildDependency edges. + Build-Project 'baseline-proxy-build' 'src/host/proxy/Host.Proxy.vcxproj' + Build-Project 'baseline-types-build' 'src/types/lib/types.vcxproj' + Build-Project 'baseline-buffer-build' 'src/buffer/out/lib/bufferout.vcxproj' + Build-Project 'baseline-render-base-build' 'src/renderer/base/lib/base.vcxproj' + Build-Project 'baseline-render-gdi-build' 'src/renderer/gdi/lib/gdi.vcxproj' Build-Project 'baseline-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' Test-Adapter 'baseline-regression' 'red' @@ -143,6 +149,7 @@ try { Test-Adapter 'patched-regression' 'focused-green' Test-Adapter 'patched-adapter' 'full-green' -Full $state.adapter_tests_verified = $true + Build-Project 'patched-propsheet-build' 'src/propsheet/propsheet.vcxproj' Build-Project 'patched-host-build' 'src/host/exe/Host.EXE.vcxproj' Build-Project 'patched-dll-build' 'src/winconpty/dll/winconptydll.vcxproj' From 5c073e49ecef6722f997cdc9a7d3b8dd97e594ee Mon Sep 17 00:00:00 2001 From: JavaZero <71128095+JavaZeroo@users.noreply.github.com> Date: Tue, 27 Oct 2026 12:21:27 +0800 Subject: [PATCH 5/5] research: preserve same-source native baseline and verified adapter evidence --- .github/workflows/conpty-upstream.yml | 8 + .../build-summary.json | 266 ++++++++++++++++++ .../validation.json | 73 +++++ experiments/conpty/upstream/README.md | 21 +- experiments/conpty/upstream/build_hosted.ps1 | 44 ++- 5 files changed, 392 insertions(+), 20 deletions(-) create mode 100644 experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json create mode 100644 experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json diff --git a/.github/workflows/conpty-upstream.yml b/.github/workflows/conpty-upstream.yml index e1c64048..2bc467ea 100644 --- a/.github/workflows/conpty-upstream.yml +++ b/.github/workflows/conpty-upstream.yml @@ -68,3 +68,11 @@ jobs: retention-days: 14 if-no-files-found: error path: upstream-result/pair/ + - name: Preserve same-source baseline for native comparison + if: success() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: conpty-upstream-baseline-pair + retention-days: 14 + if-no-files-found: error + path: upstream-result/baseline-pair/ diff --git a/experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json b/experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json new file mode 100644 index 00000000..e4958591 --- /dev/null +++ b/experiments/conpty/results/2026-10-27-upstream-margin-build/build-summary.json @@ -0,0 +1,266 @@ +{ + "schema": "termforge-upstream-conpty-build-v1", + "purpose": "isolated source-build and adapter experiment; not an official ConPTY package", + "status": "adapter-verified-runtime-unverified", + "termforge_source": "eb88c25fea343cff92bd7d5c03e74c55df6bafff", + "upstream_source": "dc8ae365847d41f62d1ebca93815bb00959bc12f", + "vcpkg_source": "927f62e4b8838bd7e441e9c45103a16ffd75007e", + "image_os": "win25-vs2026", + "image_version": "20261021.278.1", + "workflow_run": "40447628163", + "workflow_attempt": "1", + "configuration": "Release/x64/v145/WindowsSDK10.0.26100.0", + "stages": [ + { + "name": "vcpkg-tool-download", + "exit_code": 0, + "elapsed_seconds": 0.5864599, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "vcpkg-version", + "exit_code": 0, + "elapsed_seconds": 0.029197, + "log_sha256": "fea4ead8f40a8aeca48fc22406d6102a7f1e07373fc0d6a5decb129457a43161" + }, + { + "name": "nuget-restore", + "exit_code": 0, + "elapsed_seconds": 10.1804599, + "log_sha256": "c868754fca3b184523987c5c0ea4064a4c6e260955437bcb35a588298fc8d14e" + }, + { + "name": "baseline-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.0519633, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-test-patch", + "exit_code": 0, + "elapsed_seconds": 0.0446913, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "baseline-proxy-build", + "exit_code": 0, + "elapsed_seconds": 88.2472551, + "log_sha256": "30c4807f5bb76565f5f2f7de380afcde17cd814f4165771b7057b41e0d69bf60" + }, + { + "name": "baseline-types-build", + "exit_code": 0, + "elapsed_seconds": 10.7958636, + "log_sha256": "2517edeadee0c2ece0eb57d76fc9b638eb4c24dcb36bff67d7aff6bce2c01fd4" + }, + { + "name": "baseline-buffer-build", + "exit_code": 0, + "elapsed_seconds": 16.3077754, + "log_sha256": "5e0cd5be3d1fa74b0db5bd0ca246b75cad536912d1264cb2a04ce5243fe483cc" + }, + { + "name": "baseline-render-base-build", + "exit_code": 0, + "elapsed_seconds": 8.5950454, + "log_sha256": "6a80b6b730498b86e451d25f08194d592438d4f02dca945aa6dd7ad4c67919c4" + }, + { + "name": "baseline-render-gdi-build", + "exit_code": 0, + "elapsed_seconds": 7.4118507, + "log_sha256": "5ffba2be38849e5bedbc3f852b4a416fbc44eb25b859c0ad59115626ece82867" + }, + { + "name": "baseline-adapter-build", + "exit_code": 0, + "elapsed_seconds": 114.0402119, + "log_sha256": "27de5599e32a701c602340f27ae0c9ca1cdf9c42ec2b9c9ab5f169a2c204f631" + }, + { + "name": "baseline-regression", + "exit_code": 1, + "elapsed_seconds": 0.304792, + "log_sha256": "d1907fb1f5e1dd008eef1b1e5611d6533d37538df0a2a6cba0871784cb8d4988" + }, + { + "name": "baseline-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0679866, + "log_sha256": "b7aa5de14c43240cceac182a506bd51937189c7b22b4a3998c8ec37bba6f1896" + }, + { + "name": "production-patch-check", + "exit_code": 0, + "elapsed_seconds": 0.04526, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "production-patch", + "exit_code": 0, + "elapsed_seconds": 0.044372, + "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "patched-adapter-build", + "exit_code": 0, + "elapsed_seconds": 10.4338222, + "log_sha256": "29d597ee653b103114c0d7fdaad3fe694350d54f3914a0b6cc42ea251b84b928" + }, + { + "name": "patched-regression", + "exit_code": 0, + "elapsed_seconds": 0.2892821, + "log_sha256": "b0413a44a181bbd07e81dcf612d18a00423baa01da4a28d0759004dc2603873c" + }, + { + "name": "patched-regression-contract", + "exit_code": 0, + "elapsed_seconds": 0.0721329, + "log_sha256": "da281146aa232fafccb57afd0100781005fc28c4625a5a782e0513e12912573d" + }, + { + "name": "patched-adapter", + "exit_code": 0, + "elapsed_seconds": 3.0609593, + "log_sha256": "343a699ea0b2bf861cf9af70d8a93e5660f1086a73d59c3b9ab48971acd66704" + }, + { + "name": "patched-adapter-contract", + "exit_code": 0, + "elapsed_seconds": 0.0728841, + "log_sha256": "332b578d7b8603574df41dea702bfb7ef14c7e201ff24b194c57a0bf61c2ff4f" + }, + { + "name": "patched-propsheet-build", + "exit_code": 0, + "elapsed_seconds": 14.8334641, + "log_sha256": "b0ca024bae4050c4acbc7bea2ba6435710a8d7ea4b8f70fe351d79dba20f8eaf" + }, + { + "name": "patched-host-build", + "exit_code": 0, + "elapsed_seconds": 28.4543468, + "log_sha256": "3186d0fc9afea5dd466b68042787b259cf722a84d8a429850309d5a1d4e50bb8" + }, + { + "name": "patched-dll-build", + "exit_code": 0, + "elapsed_seconds": 12.3535776, + "log_sha256": "1dbb9fc9a551831d8d69ad67a7142e3247c28f92982a04bf42784be75ea8c650" + } + ], + "test_binaries": [ + { + "stage": "baseline-regression", + "test_dll_sha256": "3e161442de1785596a54f489d3c0b468a22b16ae8966a6dcb3402bbd70052856", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-regression", + "test_dll_sha256": "6a5c51aaeee2ef99eff4c0257c20e4bbaf9b4c2efb509a4d2b6a424cccb8a16b", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + }, + { + "stage": "patched-adapter", + "test_dll_sha256": "6a5c51aaeee2ef99eff4c0257c20e4bbaf9b4c2efb509a4d2b6a424cccb8a16b", + "taef_sha256": "b729c665a51a1e36dc24152ecde4e4a7efb618b75dc7387d8c6edb103ef952c1" + } + ], + "adapter_tests_verified": true, + "native_runtime_verified": false, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "vcpkg_shallow": "false", + "patch_sha256": "dc27377e399bd0217a0b76e16cc89f087c1b781929e4c958ad30c3a475322b81", + "source_hashes_verified": true, + "tools": { + "visual_studio": "18.0", + "msvc": "14.51.36231", + "sdk": "10.0.26100.0\\", + "compiler_sha256": "b0a1818ebae6da7f18230a84e76ce839212584ac4c2ab55b97a0d5268085da57", + "msbuild_sha256": "fc55d2743dbbb20708b2ca6084fc5b036a2ce72291ff989eae94ad8220ae2f74" + }, + "vcpkg_exe_sha256": "f1edbf3a39de350e2bb065214fdc057111aa87a2e2ed9a7dcb8ddc86e17751b9", + "files": { + "OpenConsole.exe": { + "sha256": "6516b5c9b890d810ba9f178d082d69f664bce775acb2820e9d40f297ae037bdb", + "bytes": 1147392 + }, + "conpty.dll": { + "sha256": "22710cf4e1ddd94bd76888717e394be556644d137b08355884649a63ee5b7590", + "bytes": 108544 + } + }, + "nuget_packages": [ + { + "name": "Appium.WebDriver.3.0.0.2.nupkg", + "sha256": "c67adece6bd5730036812af29614a7e5699f048b8e0d261841f47d2007358702" + }, + { + "name": "Castle.Core.4.1.1.nupkg", + "sha256": "c3f130d751103ec89a4ec24ec4f3c8bfa02c4d7ca70a865b68c79eddae5dea45" + }, + { + "name": "Microsoft.Internal.PGO-Helpers.Cpp.0.2.34.nupkg", + "sha256": "92aca6465e2cbe04b312d8255d15d7af2e5cfceee59fc91194c1228283bfc453" + }, + { + "name": "Microsoft.Internal.Windows.Terminal.ThemeHelpers.0.8.250811004.nupkg", + "sha256": "c70c34c5ed0b937a23285402396c3f23523496dca59ede2aaddc2914f6ea339d" + }, + { + "name": "Microsoft.MSBuildCache.AzurePipelines.0.1.283-preview.nupkg", + "sha256": "9a5a7815a175000b8c608d94727758f0c22a8e6d5f318515169cf9d003b36f82" + }, + { + "name": "Microsoft.MSBuildCache.Local.0.1.283-preview.nupkg", + "sha256": "732767cf82dc8d4e83790d9421c4c7bdbb7b5bbc6e9ca3bb2d3e79cb92dbed79" + }, + { + "name": "Microsoft.MSBuildCache.SharedCompilation.0.1.283-preview.nupkg", + "sha256": "40d947d33e2b31341870831b320de4db2926026e2e0dbdcb71099fae1a876f34" + }, + { + "name": "Microsoft.Taef.10.100.251104001.nupkg", + "sha256": "46b1290acaebc7cbcd3c4ac2522d9526cd7c4dad3a618759754619a052396a4b" + }, + { + "name": "Microsoft.UI.Xaml.2.8.4.nupkg", + "sha256": "486dbe1f271b31057ec01ecb7ce4df3eedad11efdc26569170fb128ee4a4e964" + }, + { + "name": "Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262.nupkg", + "sha256": "43c2d1c5694a846d04d5f1cb3c3a8269ebad3b0432954da2948e53b33c5ed5fb" + }, + { + "name": "Microsoft.Web.WebView2.1.0.1661.34.nupkg", + "sha256": "bad4c011c20ab68526c4c1448da05a6297586806123019ac89387fc62f46c3ce" + }, + { + "name": "Microsoft.Windows.CppWinRT.2.0.250303.1.nupkg", + "sha256": "955e3051b35db1c00177ed14ab6b7b995c3b32a53fdb6c184ea53b1dba66e439" + }, + { + "name": "Microsoft.Windows.ImplementationLibrary.1.0.250325.1.nupkg", + "sha256": "b3e3ac9077c4f5f96bd8f90785bcae47bac6ba9688d896cd04422c8972ec5b2b" + }, + { + "name": "Microsoft.WindowsPackageManager.ComInterop.1.8.1911.nupkg", + "sha256": "9b8a8a5186a554e56fcb7997b6ea48f3432813c0945a2d9ba60930d1241cfe2b" + }, + { + "name": "Newtonsoft.Json.13.0.1.nupkg", + "sha256": "2b6b52556e27e1b7913f33eedeb95568110c746bd64afff74357f1683878323a" + }, + { + "name": "Selenium.Support.3.5.0.nupkg", + "sha256": "0716656ae84c72cc31595ee2f06bf1f59311b96f813f9ada3d31d7f7f72ffb82" + }, + { + "name": "Selenium.WebDriver.3.5.0.nupkg", + "sha256": "79c41eb770a8728a15ddf27c7ac41ca433597983ac37e4db7746a52e16cec876" + } + ], + "vcpkg_status_sha256": "9957c37c4d2da3b71509a3180374a6b5ddb4126f9e1a6cffe46a9ea0c307337c" +} diff --git a/experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json b/experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json new file mode 100644 index 00000000..4bb7411a --- /dev/null +++ b/experiments/conpty/results/2026-10-27-upstream-margin-build/validation.json @@ -0,0 +1,73 @@ +{ + "schema": "termforge-upstream-margin-build-evidence-v1", + "date_utc": "2026-10-27", + "issue": 56, + "pull_request": 96, + "head": "b56058e9c4becb30f6afda3100b73acf1e552ff7", + "tree": "e5031af701d8cd1d8c710624fb7e1a2ce3b61c74", + "run_url": "https://github.com/JavaZeroo/termforge/actions/runs/40447628163", + "job_id": 122002564243, + "summary_artifact": { + "id": 12708429315, + "zip_sha256": "ec391e524557e163aa40149dc9f5c3d801a0b7cede3b67ac52aaf15dc3fe5238" + }, + "pair_artifact": { + "id": 12708509216, + "zip_sha256": "60367bbde7a1ac5839ddb3718b19604869313e8f28df60bc33b22bdaa4d8eb31" + }, + "captured_summary_sha256": "c1f7d41e60417c50524c299ac8c9ca6617ebd90244ac9e756c51d29f39bd61c5", + "summary_representation": "build-summary.json preserves parsed fields; UTF-8/LF JSON normalization is not the captured byte identity", + "tests": { + "baseline-regression": { + "expected": "red", + "exit_code": 1, + "counts": { + "total": 1, + "passed": 0, + "failed": 1, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + }, + "patched-regression": { + "expected": "focused-green", + "exit_code": 0, + "counts": { + "total": 1, + "passed": 1, + "failed": 0, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + }, + "patched-adapter": { + "expected": "full-green", + "exit_code": 0, + "counts": { + "total": 412, + "passed": 412, + "failed": 0, + "blocked": 0, + "not_run": 0, + "skipped": 0 + }, + "accepted": true + } + }, + "baseline_failure": "expected default alternate margins; actual top=1,bottom=2 at adapterTest.cpp:3006", + "first_success_only_patched_pair": true, + "same_source_baseline_pair_built": false, + "native_runtime_verified": false, + "full_r2_contract_verified": false, + "product_backend_selected": false, + "limitations": [ + "Adapter state tests do not establish native console or downstream transport behavior.", + "This v1 build contains no unpatched native pair; same-source native red/green remains unmeasured.", + "Artifacts are temporary research outputs; no WindTerm files or upstream binary bytes are committed.", + "Earlier checkout, shallow-history and build-order failures remain in PR96 and issue56." + ] +} diff --git a/experiments/conpty/upstream/README.md b/experiments/conpty/upstream/README.md index b154d27a..990f1020 100644 --- a/experiments/conpty/upstream/README.md +++ b/experiments/conpty/upstream/README.md @@ -2,15 +2,16 @@ `dc8-native-margin-candidate.patch` 是针对 Microsoft Terminal `dc8ae365847d41f62d1ebca93815bb00959bc12f` 的独立候选改动,随附 -[Microsoft MIT 原许可](MICROSOFT-LICENSE.txt)。原作者版权保留。它目前只通过完整固定文件的 -`git apply --unidiff-zero --check`,**未编译、未运行上游测试、未生成后端、未证明修复**。 +[Microsoft MIT 原许可](MICROSOFT-LICENSE.txt)。原作者版权保留。 +[首次真实构建记录](../results/2026-10-27-upstream-margin-build/validation.json)包含原实现的断言失败、 +补丁后的 focused 1/1、完整 adapter 412/412 及同源 EXE/DLL 身份;**这些不证明原生运行时或完整合同修复**。 原问题、逐字段合同与有界运行见 [#56 调查](../../../docs/research/conpty-native-margin-path.md)。 候选在同一 `AdaptDispatch` 中保存隐藏主屏 margin,新建备用屏使用默认 margin,返回主屏恢复保存值; 重复 `1049h` 不覆盖隐藏主屏值,重复 `1049l` 不清空主屏当前值。它没有改源 fixture、运输、API 表、宽度 flag、 -TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `AlternateScreenMarginsTest` 也尚未执行。 +TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `AlternateScreenMarginsTest` 固定主/备用屏切换的 margin 回归。 -## 下一次隔离构建的具体入口 +## 隔离构建的具体入口 在单独的 Microsoft Terminal 源码 checkout 固定上述提交,校验 [validation.json](../results/2026-10-26-native-margin-path/validation.json) 的源文件 SHA 后,先用 `git apply --unidiff-zero --check` 检查、再以同一选项应用本 patch。完整保留上游许可证、依赖锁和构建日志。 @@ -28,7 +29,7 @@ TermForge 默认后端或现有官方双 SHA 校验。附加的上游 `Alternate - `src/host/exe/Host.EXE.vcxproj`:生成 `OpenConsole.exe`,包含本次 adapter 修订。 - `src/winconpty/dll/winconptydll.vcxproj`:从同一完整提交/依赖/配置生成配套 DLL。 -这些入口来自同一提交的 `OpenConsole.slnx`;不是已执行或已验证的构建命令结果。 +这些入口来自同一提交的 `OpenConsole.slnx`;实际执行身份和结果由对应 PR/#56 记录。 测试入口可用上游 `Invoke-OpenConsoleTests -Test adapter`,其映射在 `tools/tests.xml`,须限制为无交互 unit 路径。 ### 一次性 hosted 构建入口 @@ -42,14 +43,18 @@ vcpkg 使用完整 Git 历史,HEAD 仍固定在原 baseline;[历史 port 的 单项目入口显式补齐固定 `OpenConsole.slnx` 的顺序依赖:adapter 前构建 Proxy/Types/Buffer/Render Base/GDI,生成所需接口头;Host.EXE 前构建 Propsheet。 [脚本](build_hosted.ps1)先只应用已有测试,编译并要求一次 `AlternateScreenMarginsTest` 的明确 margin 断言失败; -缺包、编译错误、加载错误、零测试、跳过或超时均不算 red。再仅应用两个实现文件的 patch,重建并分别执行一次 focused 和完整 adapter 测试, -通过后才从同源构建 EXE/DLL。TAEF focused/session 期限分别为60/600秒,整个构建 job 最长45分钟,没有自动重试。 +缺包、编译错误、加载错误、零测试、跳过或超时均不算 red。随后构建并单独保存原生产代码的 baseline EXE/DLL, +再仅应用两个实现文件的 patch,重建并分别执行一次 focused 和完整 adapter 测试,通过后构建 patched EXE/DLL。 +两组都来自同一 source/工具/依赖,variant 清单分别记录是否应用生产补丁及实际双 SHA,不能互相混配。 +baseline 的 variant 标明 focused 预期失败、完整 adapter 未验;共享 summary 中的 adapter 通过仅指 patched 变体。 +TAEF focused/session 期限分别为60/600秒,整个构建 job 最长45分钟,没有自动重试。 [判据](taef_result.py)和[拒绝用例](test_taef_result.py)防止把进程失败、缺失执行或局部测试当作完整结果。 TAEF 参数语义来自 [Microsoft 文档](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/te-exe-command-line-parameters), 单失败 red 只接受退出码1;[harness/超时高位](https://learn.microsoft.com/en-us/windows-hardware/drivers/taef/exit-codes-for-taef)不得混作断言失败。 workflow 保存白名单 summary、实际包/工具/双文件 SHA;完整日志仅在 CI 日志中,额外文件留在不上传的 `_private`。 -测试通过后的短期实验 artifact 带上游 LICENSE/NOTICE 和补丁,不包含 TAEF、NuGet 工具或 WindTerm 文件。 +全部门槛通过后才上传两组短期实验 artifact,均带上游 LICENSE/NOTICE 和候选补丁;baseline 清单明确该生产补丁未应用。 +它们不包含 TAEF、NuGet 工具或 WindTerm 文件。 这些是可执行的验证步骤;其实际结果由对应 PR/#56 记录,入口存在不表示已经编译或通过。 即使 adapter 通过,仍须新 fork 身份、原175B四阶段、原生/运输/候选末端及反例验证;不能加载为现有官方包或据此结项。 diff --git a/experiments/conpty/upstream/build_hosted.ps1 b/experiments/conpty/upstream/build_hosted.ps1 index ffcbb7f6..5c1b7158 100644 --- a/experiments/conpty/upstream/build_hosted.ps1 +++ b/experiments/conpty/upstream/build_hosted.ps1 @@ -21,7 +21,7 @@ foreach ($path in @($Source, $Vcpkg, $Out)) { if (Test-Path -LiteralPath $Out) { throw 'A fresh output directory is required.' } New-Item -ItemType Directory -Path "$Out/public", "$Out/_private", "$Out/cache" | Out-Null $state = [ordered]@{ - schema = 'termforge-upstream-conpty-build-v1' + schema = 'termforge-upstream-conpty-build-v2' purpose = 'isolated source-build and adapter experiment; not an official ConPTY package' status = 'running' termforge_source = (& git -C $repo rev-parse HEAD) @@ -63,6 +63,22 @@ function Build-Project([string]$Name, [string]$Project) { '/p:WindowsTargetPlatformVersion=10.0.26100.0', '/p:CL_MPCount=1', '/p:UseMultiToolTask=false', "/p:VCToolsVersion=$env:VCToolsVersion", "/p:SolutionDir=$Source\", "/p:VcpkgRoot=$Vcpkg\")) } +function Build-HostPair([string]$Prefix) { + Build-Project "$Prefix-propsheet-build" 'src/propsheet/propsheet.vcxproj' + Build-Project "$Prefix-host-build" 'src/host/exe/Host.EXE.vcxproj' + Build-Project "$Prefix-dll-build" 'src/winconpty/dll/winconptydll.vcxproj' +} +function Copy-Pair([string]$Directory) { + New-Item -ItemType Directory -Path "$Out/$Directory" | Out-Null + $files = [ordered]@{} + foreach ($name in @('OpenConsole.exe', 'conpty.dll')) { + $binary = "$Source/bin/x64/Release/$name" + $files[$name] = [ordered]@{sha256=(Digest $binary); bytes=(Get-Item -LiteralPath $binary).Length} + Copy-Item -LiteralPath $binary -Destination "$Out/$Directory/$name" + if ((Digest "$Out/$Directory/$name") -ne $files[$name].sha256) { throw 'Copied pair identity differs.' } + } + return $files +} function Test-Adapter([string]$Name, [string]$Expected, [switch]$Full) { $testBinary = "$Source/bin/x64/Release/ConAdapter.Unit.Tests.dll" $binaryHash = Digest $testBinary @@ -140,6 +156,9 @@ try { Build-Project 'baseline-render-gdi-build' 'src/renderer/gdi/lib/gdi.vcxproj' Build-Project 'baseline-adapter-build' 'src/terminal/adapter/ut_adapter/Adapter.UnitTests.vcxproj' Test-Adapter 'baseline-regression' 'red' + Build-HostPair 'baseline' + $currentStage = 'baseline-artifact-identity' + $state.baseline_files = Copy-Pair 'baseline-pair' [void](Invoke-Native 'production-patch-check' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", '--check', $patch)) [void](Invoke-Native 'production-patch' 'git' @('apply', '--unidiff-zero', "--exclude=$testFile", $patch)) @@ -149,18 +168,10 @@ try { Test-Adapter 'patched-regression' 'focused-green' Test-Adapter 'patched-adapter' 'full-green' -Full $state.adapter_tests_verified = $true - Build-Project 'patched-propsheet-build' 'src/propsheet/propsheet.vcxproj' - Build-Project 'patched-host-build' 'src/host/exe/Host.EXE.vcxproj' - Build-Project 'patched-dll-build' 'src/winconpty/dll/winconptydll.vcxproj' + Build-HostPair 'patched' $currentStage = 'artifact-identity' - New-Item -ItemType Directory -Path "$Out/pair" | Out-Null - $state.files = [ordered]@{} - foreach ($name in @('OpenConsole.exe', 'conpty.dll')) { - $binary = "$Source/bin/x64/Release/$name" - $state.files[$name] = [ordered]@{sha256=(Digest $binary); bytes=(Get-Item -LiteralPath $binary).Length} - Copy-Item -LiteralPath $binary -Destination "$Out/pair/$name" - } + $state.files = Copy-Pair 'pair' $state.nuget_packages = @() foreach ($package in Get-ChildItem -LiteralPath "$Source/packages" -Filter '*.nupkg' -Recurse -File) { $state.nuget_packages += [ordered]@{name=$package.Name; sha256=(Digest $package.FullName)} @@ -168,7 +179,16 @@ try { $state.vcpkg_status_sha256 = Digest "$Source/obj/x64/vcpkg/vcpkg/status" $state.status = 'adapter-verified-runtime-unverified' Save-State - Copy-Item -LiteralPath "$Out/public/summary.json", $patch, "$Source/LICENSE", "$Source/NOTICE.md" -Destination "$Out/pair" + foreach ($directory in @('baseline-pair', 'pair')) { + $patched = $directory -eq 'pair' + [ordered]@{schema='termforge-upstream-conpty-variant-v1'; upstream_source=$state.upstream_source; + workflow_run=$state.workflow_run; production_patch_applied=$patched; + focused_adapter_result=$(if ($patched) { 'passed' } else { 'expected-failure' }); + full_adapter_verified=$patched; + patch_sha256=$state.patch_sha256; files=$(if ($patched) { $state.files } else { $state.baseline_files }) + } | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath "$Out/$directory/variant.json" -Encoding utf8 + Copy-Item -LiteralPath "$Out/public/summary.json", $patch, "$Source/LICENSE", "$Source/NOTICE.md" -Destination "$Out/$directory" + } } catch { $state.status = 'failed' $state.failed_stage = $currentStage